Merge "Log the status code when adding or removing a tunnel mode listener fails." into main
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 4cf46e5..f01ac02 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
@@ -565,7 +565,6 @@
     }
 
     @Test
-    @Parameters(method = "getData")
     public void time_new_byteArray() throws Exception {
         final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
@@ -574,7 +573,6 @@
     }
 
     @Test
-    @Parameters(method = "getData")
     public void time_ByteBuffer_allocate() throws Exception {
         final BenchmarkState state = mBenchmarkRule.getState();
         while (state.keepRunning()) {
diff --git a/api/api.go b/api/api.go
index 5b7f534..e9f1fee 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,7 +15,7 @@
 package api
 
 import (
-	"sort"
+	"slices"
 
 	"github.com/google/blueprint/proptools"
 
@@ -75,31 +75,25 @@
 
 var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
 
-func (a *CombinedApis) bootclasspath(ctx android.ConfigAndErrorContext) []string {
-	return a.properties.Bootclasspath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
-}
-
-func (a *CombinedApis) systemServerClasspath(ctx android.ConfigAndErrorContext) []string {
-	return a.properties.System_server_classpath.GetOrDefault(a.ConfigurableEvaluator(ctx), nil)
-}
-
 func (a *CombinedApis) apiFingerprintStubDeps(ctx android.BottomUpMutatorContext) []string {
-	ret := []string{}
+	bootClasspath := a.properties.Bootclasspath.GetOrDefault(ctx, nil)
+	systemServerClasspath := a.properties.System_server_classpath.GetOrDefault(ctx, nil)
+	var ret []string
 	ret = append(
 		ret,
-		transformArray(a.bootclasspath(ctx), "", ".stubs")...,
+		transformArray(bootClasspath, "", ".stubs")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.bootclasspath(ctx), "", ".stubs.system")...,
+		transformArray(bootClasspath, "", ".stubs.system")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.bootclasspath(ctx), "", ".stubs.module_lib")...,
+		transformArray(bootClasspath, "", ".stubs.module_lib")...,
 	)
 	ret = append(
 		ret,
-		transformArray(a.systemServerClasspath(ctx), "", ".stubs.system_server")...,
+		transformArray(systemServerClasspath, "", ".stubs.system_server")...,
 	)
 	return ret
 }
@@ -129,7 +123,7 @@
 	Cmd        *string
 	Dists      []android.Dist
 	Out        []string
-	Srcs       []string
+	Srcs       proptools.Configurable[[]string]
 	Tools      []string
 	Visibility []string
 }
@@ -137,7 +131,7 @@
 type libraryProps struct {
 	Name            *string
 	Sdk_version     *string
-	Static_libs     []string
+	Static_libs     proptools.Configurable[[]string]
 	Visibility      []string
 	Defaults        []string
 	Is_stubs_module *bool
@@ -145,7 +139,7 @@
 
 type fgProps struct {
 	Name       *string
-	Srcs       []string
+	Srcs       proptools.Configurable[[]string]
 	Visibility []string
 }
 
@@ -166,7 +160,7 @@
 	// The module for the non-updatable / non-module part of the api.
 	BaseTxt string
 	// The list of modules that are relevant for this merged txt.
-	Modules []string
+	Modules proptools.Configurable[[]string]
 	// The output tag for each module to use.e.g. {.public.api.txt} for current.txt
 	ModuleTag string
 	// public, system, module-lib or system-server
@@ -190,7 +184,8 @@
 	props.Tools = []string{"metalava"}
 	props.Out = []string{filename}
 	props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --out $(out)")
-	props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...)
+	props.Srcs = proptools.NewSimpleConfigurable([]string{txt.BaseTxt})
+	props.Srcs.Append(createSrcs(txt.Modules, txt.ModuleTag))
 	if doDist {
 		props.Dists = []android.Dist{
 			{
@@ -209,11 +204,11 @@
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
-func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
+func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules proptools.Configurable[[]string]) {
 	for _, i := range []struct {
 		name    string
 		tag     string
-		modules []string
+		modules proptools.Configurable[[]string]
 	}{
 		{
 			name:    "all-modules-public-annotations",
@@ -240,33 +235,39 @@
 	}
 }
 
-func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedPublicStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
+	modules = modules.Clone()
+	transformConfigurableArray(modules, "", ".stubs")
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-modules-public-stubs")
-	props.Static_libs = transformArray(modules, "", ".stubs")
+	props.Static_libs = modules
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
+	modules = modules.Clone()
+	transformConfigurableArray(modules, "", ".stubs.exportable")
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
-	props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+	props.Static_libs = modules
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedSystemStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
 	// First create the all-updatable-modules-system-stubs
 	{
-		updatable_modules := removeAll(modules, non_updatable_modules)
+		updatable_modules := modules.Clone()
+		removeAll(updatable_modules, non_updatable_modules)
+		transformConfigurableArray(updatable_modules, "", ".stubs.system")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs")
-		props.Static_libs = transformArray(updatable_modules, "", ".stubs.system")
+		props.Static_libs = updatable_modules
 		props.Sdk_version = proptools.StringPtr("module_current")
 		props.Visibility = []string{"//frameworks/base"}
 		props.Is_stubs_module = proptools.BoolPtr(true)
@@ -275,10 +276,11 @@
 	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
 	// into all-modules-system-stubs.
 	{
+		static_libs := transformArray(non_updatable_modules, "", ".stubs.system")
+		static_libs = append(static_libs, "all-updatable-modules-system-stubs")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("all-modules-system-stubs")
-		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.system")
-		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs")
+		props.Static_libs = proptools.NewSimpleConfigurable(static_libs)
 		props.Sdk_version = proptools.StringPtr("module_current")
 		props.Visibility = []string{"//frameworks/base"}
 		props.Is_stubs_module = proptools.BoolPtr(true)
@@ -286,13 +288,15 @@
 	}
 }
 
-func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
 	// First create the all-updatable-modules-system-stubs
 	{
-		updatable_modules := removeAll(modules, non_updatable_modules)
+		updatable_modules := modules.Clone()
+		removeAll(updatable_modules, non_updatable_modules)
+		transformConfigurableArray(updatable_modules, "", ".stubs.exportable.system")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
-		props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+		props.Static_libs = updatable_modules
 		props.Sdk_version = proptools.StringPtr("module_current")
 		props.Visibility = []string{"//frameworks/base"}
 		props.Is_stubs_module = proptools.BoolPtr(true)
@@ -301,10 +305,11 @@
 	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
 	// into all-modules-system-stubs.
 	{
+		static_libs := transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+		static_libs = append(static_libs, "all-updatable-modules-system-stubs-exportable")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
-		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
-		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+		props.Static_libs = proptools.NewSimpleConfigurable(static_libs)
 		props.Sdk_version = proptools.StringPtr("module_current")
 		props.Visibility = []string{"//frameworks/base"}
 		props.Is_stubs_module = proptools.BoolPtr(true)
@@ -315,7 +320,7 @@
 func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
-	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test")
+	props.Static_libs = proptools.NewSimpleConfigurable(transformArray(non_updatable_modules, "", ".stubs.test"))
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
@@ -325,25 +330,27 @@
 func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
-	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+	props.Static_libs = proptools.NewSimpleConfigurable(transformArray(non_updatable_modules, "", ".stubs.exportable.test"))
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
+func createMergedFrameworkImpl(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
+	modules = modules.Clone()
 	// This module is for the "framework-all" module, which should not include the core libraries.
-	modules = removeAll(modules, core_libraries_modules)
+	removeAll(modules, core_libraries_modules)
 	// Remove the modules that belong to non-updatable APEXes since those are allowed to compile
 	// against unstable APIs.
-	modules = removeAll(modules, non_updatable_modules)
+	removeAll(modules, non_updatable_modules)
 	// First create updatable-framework-module-impl, which contains all updatable modules.
 	// This module compiles against module_lib SDK.
 	{
+		transformConfigurableArray(modules, "", ".impl")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("updatable-framework-module-impl")
-		props.Static_libs = transformArray(modules, "", ".impl")
+		props.Static_libs = modules
 		props.Sdk_version = proptools.StringPtr("module_current")
 		props.Visibility = []string{"//frameworks/base"}
 		ctx.CreateModule(java.LibraryFactory, &props)
@@ -352,65 +359,74 @@
 	// Now create all-framework-module-impl, which contains updatable-framework-module-impl
 	// and all non-updatable modules. This module compiles against hidden APIs.
 	{
+		static_libs := transformArray(non_updatable_modules, "", ".impl")
+		static_libs = append(static_libs, "updatable-framework-module-impl")
 		props := libraryProps{}
 		props.Name = proptools.StringPtr("all-framework-module-impl")
-		props.Static_libs = transformArray(non_updatable_modules, "", ".impl")
-		props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl")
+		props.Static_libs = proptools.NewSimpleConfigurable(static_libs)
 		props.Sdk_version = proptools.StringPtr("core_platform")
 		props.Visibility = []string{"//frameworks/base"}
 		ctx.CreateModule(java.LibraryFactory, &props)
 	}
 }
 
-func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
+	modules = modules.Clone()
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
-	modules = removeAll(modules, core_libraries_modules)
-	modules = removeAll(modules, non_updatable_modules)
+	removeAll(modules, core_libraries_modules)
+	removeAll(modules, non_updatable_modules)
+	transformConfigurableArray(modules, "", ".stubs.exportable.module_lib")
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
-	props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+	props.Static_libs = modules
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
+	modules = modules.Clone()
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
-	modules = removeAll(modules, core_libraries_modules)
-	modules = removeAll(modules, non_updatable_modules)
+	removeAll(modules, core_libraries_modules)
+	removeAll(modules, non_updatable_modules)
+	transformConfigurableArray(modules, "", ".stubs.module_lib")
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api")
-	props.Static_libs = transformArray(modules, "", ".stubs.module_lib")
+	props.Static_libs = modules
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
+func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath proptools.Configurable[[]string]) {
 	// The user of this module compiles against the "core" SDK and against non-updatable bootclasspathModules,
 	// so remove to avoid dupes.
-	bootclasspathModules := removeAll(bootclasspath, core_libraries_modules)
-	bootclasspathModules = removeAll(bootclasspath, non_updatable_modules)
-	modules := append(
-		// Include all the module-lib APIs from the bootclasspath libraries.
-		transformArray(bootclasspathModules, "", ".stubs.exportable.module_lib"),
-		// Then add all the system-server APIs from the service-* libraries.
-		transformArray(system_server_classpath, "", ".stubs.exportable.system_server")...,
-	)
+	bootclasspathModules := bootclasspath.Clone()
+	removeAll(bootclasspathModules, core_libraries_modules)
+	removeAll(bootclasspathModules, non_updatable_modules)
+	transformConfigurableArray(bootclasspathModules, "", ".stubs.exportable.module_lib")
+
+	system_server_classpath = system_server_classpath.Clone()
+	transformConfigurableArray(system_server_classpath, "", ".stubs.exportable.system_server")
+
+	// Include all the module-lib APIs from the bootclasspath libraries.
+	// Then add all the system-server APIs from the service-* libraries.
+	bootclasspathModules.Append(system_server_classpath)
+
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("framework-updatable-stubs-system_server_api-exportable")
-	props.Static_libs = modules
+	props.Static_libs = bootclasspathModules
 	props.Sdk_version = proptools.StringPtr("system_server_current")
 	props.Visibility = []string{"//frameworks/base"}
 	props.Is_stubs_module = proptools.BoolPtr(true)
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
-func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) {
+func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
 	props := fgProps{}
 	props.Name = proptools.StringPtr("all-modules-public-stubs-source")
 	props.Srcs = createSrcs(modules, "{.public.stubs.source}")
@@ -418,7 +434,14 @@
 	ctx.CreateModule(android.FileGroupFactory, &props)
 }
 
-func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string, baseTxtModulePrefix, stubsTypeSuffix string, doDist bool) {
+func createMergedTxts(
+	ctx android.LoadHookContext,
+	bootclasspath proptools.Configurable[[]string],
+	system_server_classpath proptools.Configurable[[]string],
+	baseTxtModulePrefix string,
+	stubsTypeSuffix string,
+	doDist bool,
+) {
 	var textFiles []MergedTxtDefinition
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
@@ -463,11 +486,10 @@
 }
 
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
-	bootclasspath := a.bootclasspath(ctx)
-	system_server_classpath := a.systemServerClasspath(ctx)
+	bootclasspath := a.properties.Bootclasspath.Clone()
+	system_server_classpath := a.properties.System_server_classpath.Clone()
 	if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
-		bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
-		sort.Strings(bootclasspath)
+		bootclasspath.AppendSimpleValue(a.properties.Conditional_bootclasspath)
 	}
 	createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-", "-", false)
 	createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-exportable-", "-exportable-", true)
@@ -500,8 +522,10 @@
 // Various utility methods below.
 
 // Creates an array of ":<m><tag>" for each m in <modules>.
-func createSrcs(modules []string, tag string) []string {
-	return transformArray(modules, ":", tag)
+func createSrcs(modules proptools.Configurable[[]string], tag string) proptools.Configurable[[]string] {
+	result := modules.Clone()
+	transformConfigurableArray(result, ":", tag)
+	return result
 }
 
 // Creates an array of "<prefix><m><suffix>", for each m in <modules>.
@@ -513,11 +537,23 @@
 	return a
 }
 
-func removeAll(s []string, vs []string) []string {
-	for _, v := range vs {
-		s = remove(s, v)
-	}
-	return s
+// Creates an array of "<prefix><m><suffix>", for each m in <modules>.
+func transformConfigurableArray(modules proptools.Configurable[[]string], prefix, suffix string) {
+	modules.AddPostProcessor(func(s []string) []string {
+		return transformArray(s, prefix, suffix)
+	})
+}
+
+func removeAll(s proptools.Configurable[[]string], vs []string) {
+	s.AddPostProcessor(func(s []string) []string {
+		a := make([]string, 0, len(s))
+		for _, module := range s {
+			if !slices.Contains(vs, module) {
+				a = append(a, module)
+			}
+		}
+		return a
+	})
 }
 
 func remove(s []string, v string) []string {
diff --git a/cmds/uinput/examples/test-touchpad.evemu b/cmds/uinput/examples/test-touchpad.evemu
new file mode 100644
index 0000000..34ee572
--- /dev/null
+++ b/cmds/uinput/examples/test-touchpad.evemu
@@ -0,0 +1,44 @@
+# EVEMU 1.2
+# This is an evemu "recording" of an Apple Magic Trackpad (1st generation), but
+# that doesn't actually make any movements. It just runs for a very long time,
+# to make Android think a touchpad is connected. This is useful for testing
+# things like the settings in System > Touchpad, which only appear when one is
+# connected.
+#
+# It can be played by piping it to the uinput command over ADB:
+#     $ adb shell uinput - < test-touchpad.evemu
+N: Fake touchpad
+I: 0005 05ac 030e 0160
+P: 05 00 00 00 00 00 00 00
+B: 00 0b 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 01 00 00 00 00 00
+B: 01 20 e5 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 03 00 00 00 00 80 73 02
+B: 04 10 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+A: 00 -2909 3167 4 0 46
+A: 01 -2456 2565 4 0 45
+A: 2f 0 15 0 0 0
+A: 30 0 1020 4 0 0
+A: 31 0 1020 4 0 0
+A: 34 -31 32 1 0 0
+A: 35 -2909 3167 4 0 46
+A: 36 -2456 2565 4 0 45
+A: 39 0 65535 0 0 0
+E: 0.000001 0004 0005 1234
+E: 0.000001 0000 0000 0000
+E: 1000000000.000000 0004 0005 1235
+E: 1000000000.000000 0000 0000 0000
diff --git a/core/api/current.txt b/core/api/current.txt
index 5685221..5e8febe 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -145,12 +145,12 @@
     field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT";
     field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL";
     field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
-    field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL";
+    field public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL";
     field public static final String MANAGE_DEVICE_POLICY_BLUETOOTH = "android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH";
     field public static final String MANAGE_DEVICE_POLICY_BUGREPORT = "android.permission.MANAGE_DEVICE_POLICY_BUGREPORT";
     field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS";
     field public static final String MANAGE_DEVICE_POLICY_CAMERA = "android.permission.MANAGE_DEVICE_POLICY_CAMERA";
-    field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE";
+    field public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE";
     field public static final String MANAGE_DEVICE_POLICY_CERTIFICATES = "android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES";
     field public static final String MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE = "android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE";
     field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String MANAGE_DEVICE_POLICY_CONTENT_PROTECTION = "android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION";
@@ -172,7 +172,7 @@
     field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
     field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA";
     field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE";
-    field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE";
+    field public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE";
     field public static final String MANAGE_DEVICE_POLICY_MOBILE_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK";
     field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS";
     field public static final String MANAGE_DEVICE_POLICY_MTE = "android.permission.MANAGE_DEVICE_POLICY_MTE";
@@ -8121,7 +8121,7 @@
     method public boolean isLogoutEnabled();
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
-    method @FlaggedApi("android.app.admin.flags.is_mte_policy_enforced") public static boolean isMtePolicyEnforced();
+    method public static boolean isMtePolicyEnforced();
     method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
     method public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
@@ -8597,7 +8597,7 @@
     field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452
     field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451
     field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
-    field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c
+    field public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c
     field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477
     field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478
     field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ba16037..60dc52b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1322,7 +1322,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder();
-    method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle);
@@ -1349,7 +1349,7 @@
     method @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);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
     method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 42f7615..4fc7076 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2003,6 +2003,7 @@
     method public void setRampingRingerEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setVolumeControllerLongPressTimeoutEnabled(boolean);
     method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes);
   }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8fd3326..f27dc32 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -79,7 +79,6 @@
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.Pools;
@@ -3156,12 +3155,6 @@
     /** @hide */
     public static final String KEY_HISTORICAL_OPS = "historical_ops";
 
-    /** System properties for debug logging of noteOp call sites */
-    private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled";
-    private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages";
-    private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
-    private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
-
     /**
      * Retrieve the op switch that controls the given operation.
      * @hide
@@ -8066,14 +8059,6 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(int code, int uid, @Mode int mode) {
         try {
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (code == OP_BLUETOOTH_CONNECT) {
-                Log.i(DEBUG_LOGGING_TAG,
-                        "setUidMode called for OP_BLUETOOTH_CONNECT with mode: " + mode
-                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
-                                + " trace: "
-                                + Arrays.toString(Thread.currentThread().getStackTrace()));
-            }
             mService.setUidMode(code, uid, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -8094,15 +8079,6 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
         try {
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (appOp.equals(OPSTR_BLUETOOTH_CONNECT)) {
-                Log.i(DEBUG_LOGGING_TAG,
-                        "setUidMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
-                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
-                                + " trace: "
-                                + Arrays.toString(Thread.currentThread().getStackTrace()));
-            }
-
             mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -8143,14 +8119,6 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setMode(int code, int uid, String packageName, @Mode int mode) {
         try {
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (code == OP_BLUETOOTH_CONNECT) {
-                Log.i(DEBUG_LOGGING_TAG,
-                        "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
-                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
-                                + " trace: "
-                                + Arrays.toString(Thread.currentThread().getStackTrace()));
-            }
             mService.setMode(code, uid, packageName, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -8173,14 +8141,6 @@
     public void setMode(@NonNull String op, int uid, @Nullable String packageName,
             @Mode int mode) {
         try {
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (op.equals(OPSTR_BLUETOOTH_CONNECT)) {
-                Log.i(DEBUG_LOGGING_TAG,
-                        "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
-                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
-                                + " trace: "
-                                + Arrays.toString(Thread.currentThread().getStackTrace()));
-            }
             mService.setMode(strOpToOp(op), uid, packageName, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 4c97dbb..4d61f41 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -106,6 +106,17 @@
 
 flag {
      namespace: "backstage_power"
+     name: "use_app_info_not_launched"
+     description: "Use the notLaunched state from ApplicationInfo instead of current value"
+     is_fixed_read_only: true
+     bug: "362516211"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "backstage_power"
      name: "cache_get_current_user_id"
      description: "Add caching for getCurrentUserId"
      is_fixed_read_only: true
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0f54cb7..daa15f0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,10 +55,8 @@
 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_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
-import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -4232,7 +4230,6 @@
      *
      * @return whether MTE is currently enabled on the device.
      */
-    @FlaggedApi(FLAG_IS_MTE_POLICY_ENFORCED)
     public static boolean isMtePolicyEnforced() {
         return Zygote.nativeSupportsMemoryTagging();
     }
@@ -8664,6 +8661,7 @@
      *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA}.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_CAMERA, conditional = true)
+    @SupportsCoexistence
     public void setCameraDisabled(@Nullable ComponentName admin, boolean disabled) {
         if (mService != null) {
             try {
@@ -10249,6 +10247,7 @@
      * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_TASK, conditional = true)
+    @SupportsCoexistence
     public void clearPackagePersistentPreferredActivities(@Nullable ComponentName admin,
             String packageName) {
         throwIfParentInstance("clearPackagePersistentPreferredActivities");
@@ -11940,6 +11939,7 @@
      * @throws SecurityException if {@code admin} is not a device or profile owner and if the caller
      * has not been granted the permission to set the given user restriction.
      */
+    @SupportsCoexistence
     public void addUserRestriction(@NonNull ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
         if (mService != null) {
@@ -12021,6 +12021,7 @@
      * @throws IllegalStateException if caller is not targeting Android
      * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above.
      */
+    @SupportsCoexistence
     public void addUserRestrictionGlobally(@NonNull @UserManager.UserRestrictionKey String key) {
         throwIfParentInstance("addUserRestrictionGlobally");
         if (mService != null) {
@@ -12076,6 +12077,7 @@
      * @throws SecurityException if {@code admin} is not a device or profile owner  and if the
      *  caller has not been granted the permission to set the given user restriction.
      */
+    @SupportsCoexistence
     public void clearUserRestriction(@NonNull ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
         if (mService != null) {
@@ -12312,6 +12314,7 @@
      * @see #DELEGATION_PACKAGE_ACCESS
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_PACKAGE_STATE, conditional = true)
+    @SupportsCoexistence
     public boolean setApplicationHidden(@Nullable ComponentName admin, String packageName,
             boolean hidden) {
         if (mService != null) {
@@ -12492,6 +12495,7 @@
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true)
+    @SupportsCoexistence
     public void setAccountManagementDisabled(@Nullable ComponentName admin, String accountType,
             boolean disabled) {
         if (mService != null) {
@@ -12575,10 +12579,24 @@
      **/
     @SystemApi
     public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
+        setSecondaryLockscreenEnabled(admin, enabled, null);
+    }
+
+    /**
+     * Called by the system supervision app to set whether a secondary lockscreen needs to be shown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+     *              caller is not a device admin.
+     * @param enabled Whether or not the lockscreen needs to be shown.
+     * @param options A {@link PersistableBundle} to supply options to the lock screen.
+     * @hide
+     */
+    public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled,
+            @Nullable PersistableBundle options) {
         throwIfParentInstance("setSecondaryLockscreenEnabled");
         if (mService != null) {
             try {
-                mService.setSecondaryLockscreenEnabled(admin, enabled);
+                mService.setSecondaryLockscreenEnabled(admin, enabled, options);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -14276,6 +14294,7 @@
      * @see #retrieveSecurityLogs
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_SECURITY_LOGGING, conditional = true)
+    @SupportsCoexistence
     public void setSecurityLoggingEnabled(@Nullable ComponentName admin, boolean enabled) {
         throwIfParentInstance("setSecurityLoggingEnabled");
         try {
@@ -17181,6 +17200,7 @@
      * if USB data signaling fails to be enabled/disabled.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, conditional = true)
+    @SupportsCoexistence
     public void setUsbDataSignalingEnabled(boolean enabled) {
         throwIfParentInstance("setUsbDataSignalingEnabled");
         if (mService != null) {
@@ -17746,7 +17766,6 @@
      */
     @SystemApi
     @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
-    @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED)
     public void setMaxPolicyStorageLimit(int storageLimit) {
         if (mService != null) {
             try {
@@ -17766,7 +17785,6 @@
      */
     @SystemApi
     @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
-    @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED)
     public int getMaxPolicyStorageLimit() {
         if (mService != null) {
             try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d4e5c99..a4e2b8f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -303,7 +303,7 @@
     String[] getAccountTypesWithManagementDisabled(String callerPackageName);
     String[] getAccountTypesWithManagementDisabledAsUser(int userId, String callerPackageName, in boolean parent);
 
-    void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
+    void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled, in PersistableBundle options);
     boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
 
     void setPreferentialNetworkServiceConfigs(
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 477f2e0..beb93fd 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,10 +16,7 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED;
-
 import android.Manifest;
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -611,7 +608,6 @@
      * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
      * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
      */
-    @FlaggedApi(FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
     public static final int TAG_BACKUP_SERVICE_TOGGLED =
             SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
     /**
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 9ed5aa6..8e08a95 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -13,6 +13,7 @@
   bug: "289520697"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "device_policy_size_tracking_enabled"
   is_exported: true
@@ -22,13 +23,6 @@
 }
 
 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"
-}
-
-flag {
   name: "onboarding_bugreport_v2_enabled"
   is_exported: true
   namespace: "enterprise"
@@ -44,13 +38,7 @@
   is_fixed_read_only: true
 }
 
-flag {
-  name: "dedicated_device_control_enabled"
-  namespace: "enterprise"
-  description: "Allow the device management role holder to control which platform features are available on dedicated devices."
-  bug: "281964214"
-}
-
+# Fully rolled out and must not be used.
 flag {
   name: "dedicated_device_control_api_enabled"
   is_exported: true
@@ -170,6 +158,7 @@
   bug: "293441361"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "assist_content_user_restriction_enabled"
   is_exported: true
@@ -188,6 +177,7 @@
     }
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "backup_service_security_log_event_enabled"
   is_exported: true
@@ -196,6 +186,7 @@
   bug: "304999634"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "esim_management_enabled"
   is_exported: true
@@ -213,6 +204,7 @@
   bug: "289515470"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "is_mte_policy_enforced"
   is_exported: true
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index c4e8b41..c4bfae9 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -72,14 +72,32 @@
      * we need to have per-package app function schemas.
      *
      * <p>This schema should be set visible to callers from the package owner itself and for callers
-     * with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
-     * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+     * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+     * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permissions.
      *
      * @param packageName The package name to create a schema for.
      */
     @NonNull
     public static AppSearchSchema createAppFunctionRuntimeSchema(@NonNull String packageName) {
-        return new AppSearchSchema.Builder(getRuntimeSchemaNameForPackage(packageName))
+        return getAppFunctionRuntimeSchemaBuilder(getRuntimeSchemaNameForPackage(packageName))
+                .addParentType(RUNTIME_SCHEMA_TYPE)
+                .build();
+    }
+
+    /**
+     * Creates a parent schema for all app function runtime schemas.
+     *
+     * <p>This schema should be set visible to the owner itself and for callers with {@link
+     * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
+     * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+     */
+    public static AppSearchSchema createParentAppFunctionRuntimeSchema() {
+        return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build();
+    }
+
+    private static AppSearchSchema.Builder getAppFunctionRuntimeSchemaBuilder(
+            @NonNull String schemaType) {
+        return new AppSearchSchema.Builder(schemaType)
                 .addProperty(
                         new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_FUNCTION_ID)
                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
@@ -111,9 +129,7 @@
                                 .setJoinableValueType(
                                         AppSearchSchema.StringPropertyConfig
                                                 .JOINABLE_VALUE_TYPE_QUALIFIED_ID)
-                                .build())
-                .addParentType(RUNTIME_SCHEMA_TYPE)
-                .build();
+                                .build());
     }
 
     /** Returns the function id. This might look like "com.example.message#send_message". */
diff --git a/core/java/android/app/jank/OWNERS b/core/java/android/app/jank/OWNERS
new file mode 100644
index 0000000..806de57
--- /dev/null
+++ b/core/java/android/app/jank/OWNERS
@@ -0,0 +1,4 @@
+steventerrell@google.com
+carmenjackson@google.com
+jjaggi@google.com
+pmuetschard@google.com
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 297fe8a..748260b 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -98,11 +98,11 @@
 }
 
 flag {
-  name: "camera_multiple_input_streams"
-  is_exported: true
-  namespace: "virtual_devices"
-  description: "Expose multiple surface for the virtual camera owner for different stream resolution"
-  bug: "341083465"
+    name: "camera_multiple_input_streams"
+    is_exported: true
+    namespace: "virtual_devices"
+    description: "Expose multiple surface for the virtual camera owner for different stream resolution"
+    bug: "341083465"
 }
 
 flag {
@@ -113,8 +113,16 @@
 }
 
 flag {
-  name: "status_bar_and_insets"
-  namespace: "virtual_devices"
-  description: "Allow for status bar and insets on virtual devices"
-  bug: "350007866"
+    namespace: "virtual_devices"
+    name: "display_power_manager_apis"
+    description: "Make relevant PowerManager APIs display aware by default"
+    bug: "365042486"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "status_bar_and_insets"
+    namespace: "virtual_devices"
+    description: "Allow for status bar and insets on virtual devices"
+    bug: "350007866"
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 495ae60..34bea1a 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -849,6 +849,12 @@
      */
     public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
 
+    /**
+     * Whether the app has been previously not launched
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_EXT_NOT_LAUNCHED = 1 << 6;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
             PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -857,6 +863,7 @@
             PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
             PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
             PRIVATE_FLAG_EXT_CPU_OVERRIDE,
+            PRIVATE_FLAG_EXT_NOT_LAUNCHED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlagsExt {}
@@ -2664,6 +2671,22 @@
     }
 
     /**
+     * Returns whether the app in the STOPPED state.
+     * @hide
+     */
+    public boolean isStopped() {
+        return (flags & ApplicationInfo.FLAG_STOPPED) != 0;
+    }
+
+    /**
+     * Returns whether the app was never launched (any process started) before.
+     * @hide
+     */
+    public boolean isNotLaunched() {
+        return (privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED) != 0;
+    }
+
+    /**
      * Checks if a changeId is enabled for the current user
      * @param changeId The changeId to verify
      * @return True of the changeId is enabled
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 28534ad..9eec7a4 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -426,4 +426,13 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
+
+
+flag {
+    name: "caching_development_improvements"
+    namespace: "multiuser"
+    description: "System API to simplify caching implamentations"
+    bug: "364947162"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 98904fe..0a05f70 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5150,13 +5150,19 @@
         public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
 
         /**
-         * The screen backlight brightness between 0 and 255.
+         * The screen backlight brightness between 1 (minimum) and 255 (maximum).
+         *
+         * Use {@link android.view.WindowManager.LayoutParams#screenBrightness} to set the screen
+         * brightness instead.
          */
         @Readable
         public static final String SCREEN_BRIGHTNESS = "screen_brightness";
 
         /**
-         * Control whether to enable automatic brightness mode.
+         * Controls whether to enable automatic brightness mode. Value can be set to
+         * {@link #SCREEN_BRIGHTNESS_MODE_MANUAL} or {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC}.
+         * If {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC} is set, the system may change
+         * {@link #SCREEN_BRIGHTNESS} automatically.
          */
         @Readable
         public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 5d84d17..bbe9cdb 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -35,20 +35,6 @@
     }
 
     /**
-     * @see Flags#phraseStrictFallback()
-     */
-    public static boolean phraseStrictFallback() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_PHRASE_STRICT_FALLBACK);
-    }
-
-    /**
-     * @see Flags#useBoundsForWidth()
-     */
-    public static boolean useBoundsForWidth() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
-    }
-
-    /**
      * @see Flags#fixLineHeightForLocale()
      */
     public static boolean fixLineHeightForLocale() {
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9e02460..0f1b031 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -56,8 +56,6 @@
      */
     public static final String[] TEXT_ACONFIGS_FLAGS = {
             Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
-            Flags.FLAG_PHRASE_STRICT_FALLBACK,
-            Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
             Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
             Flags.FLAG_ICU_BIDI_MIGRATION,
             Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
@@ -70,8 +68,6 @@
      */
     public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
             Flags.noBreakNoHyphenationSpan(),
-            Flags.phraseStrictFallback(),
-            Flags.useBoundsForWidth(),
             Flags.fixLineHeightForLocale(),
             Flags.icuBidiMigration(),
             Flags.fixMisalignedContextMenu(),
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index d33c95e..b2be1a7 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -2,14 +2,6 @@
 container: "system"
 
 flag {
-  name: "vendor_custom_locale_fallback"
-  namespace: "text"
-  description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
-  is_fixed_read_only: true
-  bug: "278768958"
-}
-
-flag {
   name: "new_fonts_fallback_xml"
   is_exported: true
   namespace: "text"
@@ -20,13 +12,6 @@
 }
 
 flag {
-  name: "fix_double_underline"
-  namespace: "text"
-  description: "Feature flag for fixing double underline because of the multiple font used in the single line."
-  bug: "297336724"
-}
-
-flag {
   name: "fix_line_height_for_locale"
   is_exported: true
   namespace: "text"
@@ -66,13 +51,6 @@
 }
 
 flag {
-  name: "phrase_strict_fallback"
-  namespace: "text"
-  description: "Feature flag for automatic fallback from phrase based line break to strict line break."
-  bug: "281970875"
-}
-
-flag {
   name: "use_bounds_for_width"
   is_exported: true
   namespace: "text"
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index eb35817..a395c1a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1447,6 +1447,11 @@
                         }
 
                         @Override
+                        public void onNullBinding(ComponentName name) {
+                            context.unbindService(this);
+                        }
+
+                        @Override
                         public void onServiceDisconnected(ComponentName componentName) { }
                     });
 
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 2f28a87..118edc2 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -241,6 +241,11 @@
         }
 
         @Override
+        public void onNullBinding(ComponentName name) {
+            enqueueDeferredUnbindServiceMessage();
+        }
+
+        @Override
         public void handleMessage(Message msg) {
             RemoteViewsAdapter adapter = mAdapter.get();
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a4b28ad..ef941da 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1659,11 +1659,7 @@
         }
 
         if (!hasUseBoundForWidthValue) {
-            if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
-                mUseBoundsForWidth = Flags.useBoundsForWidth();
-            } else {
-                mUseBoundsForWidth = false;
-            }
+            mUseBoundsForWidth = CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH);
         }
 
         // TODO(b/179693024): Use a ChangeId instead.
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index cdac097..1709ca7 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -404,6 +404,17 @@
     }
 
     public static void redirectLogStreams$ravenwood() {
+        if (sOut$ravenwood != null && sErr$ravenwood != null) {
+            return; // Already initialized.
+        }
+
+        // Make sure the Log class is loaded and the JNI methods are hooked up,
+        // before redirecting System.out/err.
+        // Otherwise, because ClassLoadHook tries to write to System.out, this would cause
+        // a circular initialization problem and would cause a UnsatisfiedLinkError
+        // on the JNI methods.
+        Log.isLoggable("X", Log.VERBOSE);
+
         if (sOut$ravenwood == null) {
             sOut$ravenwood = System.out;
             System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 58818f3..4708be8 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1144,7 +1144,8 @@
             mDrawLegacyNavigationBarBackground =
                     ((requestedVisibleTypes | mLastForceConsumingTypes)
                             & WindowInsets.Type.navigationBars()) != 0
-                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
+                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
+                    && navBarSize > 0;
             if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
                 mDrawLegacyNavigationBarBackgroundHandled =
                         mWindow.onDrawLegacyNavigationBarBackgroundChanged(
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 1fd933f..e440dc9 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -34,7 +34,6 @@
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA;
 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE;
@@ -72,7 +71,6 @@
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -103,6 +101,7 @@
     private final ProtoLogDataSource mDataSource;
     @Nullable
     private final ProtoLogViewerConfigReader mViewerConfigReader;
+    @Deprecated
     @Nullable
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
     @NonNull
@@ -148,6 +147,7 @@
                 cacheUpdater, groups);
     }
 
+    @Deprecated
     @VisibleForTesting
     public PerfettoProtoLogImpl(
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@@ -160,6 +160,18 @@
                 groups, dataSourceBuilder, configurationService);
     }
 
+    @VisibleForTesting
+    public PerfettoProtoLogImpl(
+            @Nullable String viewerConfigFilePath,
+            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups,
+            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
+            @NonNull ProtoLogConfigurationService configurationService) {
+        this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater,
+                groups, dataSourceBuilder, configurationService);
+    }
+
     private PerfettoProtoLogImpl(
             @Nullable String viewerConfigFilePath,
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@@ -449,6 +461,7 @@
         Log.d(LOG_TAG, "Finished onTracingFlush");
     }
 
+    @Deprecated
     private void dumpViewerConfig() {
         if (mViewerConfigInputStreamProvider == null) {
             // No viewer config available
@@ -457,103 +470,29 @@
 
         Log.d(LOG_TAG, "Dumping viewer config to trace");
 
-        mDataSource.trace(ctx -> {
-            try {
-                ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
-
-                final ProtoOutputStream os = ctx.newTracePacket();
-
-                os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
-
-                final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
-                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    if (pis.getFieldNumber() == (int) MESSAGES) {
-                        writeViewerConfigMessage(pis, os);
-                    }
-
-                    if (pis.getFieldNumber() == (int) GROUPS) {
-                        writeViewerConfigGroup(pis, os);
-                    }
-                }
-
-                os.end(outProtologViewerConfigToken);
-            } catch (IOException e) {
-                Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
-            }
-        });
+        Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
 
         Log.d(LOG_TAG, "Dumped viewer config to trace");
     }
 
-    private static void writeViewerConfigGroup(
-            ProtoInputStream pis, ProtoOutputStream os) throws IOException {
-        final long inGroupToken = pis.start(GROUPS);
-        final long outGroupToken = os.start(GROUPS);
-
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (pis.getFieldNumber()) {
-                case (int) ID:
-                    int id = pis.readInt(ID);
-                    os.write(ID, id);
-                    break;
-                case (int) NAME:
-                    String name = pis.readString(NAME);
-                    os.write(NAME, name);
-                    break;
-                case (int) TAG:
-                    String tag = pis.readString(TAG);
-                    os.write(TAG, tag);
-                    break;
-                default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
-            }
-        }
-
-        pis.end(inGroupToken);
-        os.end(outGroupToken);
-    }
-
-    private static void writeViewerConfigMessage(
-            ProtoInputStream pis, ProtoOutputStream os) throws IOException {
-        final long inMessageToken = pis.start(MESSAGES);
-        final long outMessagesToken = os.start(MESSAGES);
-
-        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (pis.getFieldNumber()) {
-                case (int) MessageData.MESSAGE_ID:
-                    os.write(MessageData.MESSAGE_ID,
-                            pis.readLong(MessageData.MESSAGE_ID));
-                    break;
-                case (int) MESSAGE:
-                    os.write(MESSAGE, pis.readString(MESSAGE));
-                    break;
-                case (int) LEVEL:
-                    os.write(LEVEL, pis.readInt(LEVEL));
-                    break;
-                case (int) GROUP_ID:
-                    os.write(GROUP_ID, pis.readInt(GROUP_ID));
-                    break;
-                case (int) LOCATION:
-                    os.write(LOCATION, pis.readString(LOCATION));
-                    break;
-                default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
-            }
-        }
-
-        pis.end(inMessageToken);
-        os.end(outMessagesToken);
-    }
-
     private void logToLogcat(String tag, LogLevel level, Message message,
             @Nullable Object[] args) {
         String messageString;
         if (mViewerConfigReader == null) {
             messageString = message.getMessage();
+
+            if (messageString == null) {
+                Log.e(LOG_TAG, "Failed to decode message for logcat. "
+                        + "Message not available without ViewerConfig to decode the hash.");
+            }
         } else {
             messageString = message.getMessage(mViewerConfigReader);
+
+            if (messageString == null) {
+                Log.e(LOG_TAG, "Failed to decode message for logcat. "
+                        + "Message hash either not available in viewerConfig file or "
+                        + "not loaded into memory from file before decoding.");
+            }
         }
 
         if (messageString == null) {
@@ -760,7 +699,7 @@
 
             os.write(MessageData.MESSAGE_ID, messageHash);
             os.write(MESSAGE, message);
-            os.write(LEVEL, level.ordinal());
+            os.write(LEVEL, level.id);
             os.write(GROUP_ID, logGroup.getId());
 
             os.end(messageConfigToken);
@@ -954,8 +893,7 @@
     private static class Message {
         @Nullable
         private final Long mMessageHash;
-        @Nullable
-        private final Integer mMessageMask;
+        private final int mMessageMask;
         @Nullable
         private final String mMessageString;
 
@@ -972,8 +910,7 @@
             this.mMessageString = messageString;
         }
 
-        @Nullable
-        private Integer getMessageMask() {
+        private int getMessageMask() {
             return mMessageMask;
         }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index d54a80b..7031d69 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -26,8 +26,6 @@
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
-import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG;
-import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,7 +34,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemClock;
 import android.tracing.perfetto.DataSourceParams;
 import android.tracing.perfetto.InitArguments;
 import android.tracing.perfetto.Producer;
@@ -125,7 +122,8 @@
         this(ProtoLogDataSource::new, tracer);
     }
 
-    private ProtoLogConfigurationService(
+    @VisibleForTesting
+    public ProtoLogConfigurationService(
             @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
             @NonNull ViewerConfigFileTracer tracer) {
         mDataSource = dataSourceBuilder.build(
@@ -374,32 +372,13 @@
 
     private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
             @NonNull String viewerConfigFilePath) {
-        dataSource.trace(ctx -> {
-            final ProtoInputStream pis;
+        Utils.dumpViewerConfig(dataSource, () -> {
             try {
-                pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
             } catch (FileNotFoundException e) {
                 throw new RuntimeException(
                         "Failed to load viewer config file " + viewerConfigFilePath, e);
             }
-
-            try {
-                final ProtoOutputStream os = ctx.newTracePacket();
-
-                os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
-
-                final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
-                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    switch (pis.getFieldNumber()) {
-                        case (int) MESSAGES -> writeViewerConfigMessage(pis, os);
-                        case (int) GROUPS -> writeViewerConfigGroup(pis, os);
-                    }
-                }
-
-                os.end(outProtologViewerConfigToken);
-            } catch (IOException e) {
-                Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
-            }
         });
     }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 1b2f5f7..0afb135 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -283,10 +283,24 @@
     public static class Instance extends DataSourceInstance {
 
         public interface TracingInstanceStartCallback {
+            /**
+             * Execute the tracing instance's onStart callback.
+             * @param instanceIdx The index of the tracing instance we are executing the callback
+             *                    for.
+             * @param config The protolog configuration for the tracing instance we are executing
+             *               the callback for.
+             */
             void run(int instanceIdx, @NonNull ProtoLogConfig config);
         }
 
         public interface TracingInstanceStopCallback {
+            /**
+             * Execute the tracing instance's onStop callback.
+             * @param instanceIdx The index of the tracing instance we are executing the callback
+             *                    for.
+             * @param config The protolog configuration for the tracing instance we are executing
+             *               the callback for.
+             */
             void run(int instanceIdx, @NonNull ProtoLogConfig config);
         }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index da6d8cf..7bdcf2d 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
 
 import android.annotation.Nullable;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.IProtoLog;
@@ -37,6 +38,8 @@
  * A service for the ProtoLog logging system.
  */
 public class ProtoLogImpl {
+    private static final String LOG_TAG = "ProtoLogImpl";
+
     private static IProtoLog sServiceInstance = null;
 
     @ProtoLogToolInjected(VIEWER_CONFIG_PATH)
@@ -97,6 +100,9 @@
      */
     public static synchronized IProtoLog getSingleInstance() {
         if (sServiceInstance == null) {
+            Log.i(LOG_TAG, "Setting up " + ProtoLogImpl.class.getSimpleName() + " with "
+                    + "viewerConfigPath = " + sViewerConfigPath);
+
             final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
 
             if (android.tracing.Flags.perfettoProtologTracing()) {
@@ -105,6 +111,9 @@
                     // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
                     // In some tests the viewer config file might not exist in which we don't
                     // want to provide config path to the user
+                    Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+                            + ProtoLogImpl.class.getSimpleName() + ". "
+                            + "Setting up without a viewer config instead...");
                     sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
                 } else {
                     sServiceInstance =
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 6c8996e..0a80e00 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -43,6 +43,13 @@
         return mLogMessageMap.get(messageHash);
     }
 
+    /**
+     * Load the viewer configs for the target groups into memory.
+     * Only viewer configs loaded into memory can be required. So this must be called for all groups
+     * we want to query before we query their viewer config.
+     *
+     * @param groups Groups to load the viewer configs from file into memory.
+     */
     public synchronized void loadViewerConfig(@NonNull String[] groups) {
         loadViewerConfig(groups, (message) -> {});
     }
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
new file mode 100644
index 0000000..1e6ba30
--- /dev/null
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -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.internal.protolog;
+
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
+
+import android.annotation.NonNull;
+import android.internal.perfetto.protos.Protolog;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+
+public class Utils {
+    private static final String LOG_TAG = "ProtoLogUtils";
+
+    /**
+     * Dump the viewer config provided by the input stream to the target datasource.
+     * @param dataSource The datasource to dump the ProtoLog viewer config to.
+     * @param viewerConfigInputStreamProvider The InputStream that provided the proto viewer config.
+     */
+    public static void dumpViewerConfig(@NonNull ProtoLogDataSource dataSource,
+            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+        dataSource.trace(ctx -> {
+            try {
+                ProtoInputStream pis = viewerConfigInputStreamProvider.getInputStream();
+
+                final ProtoOutputStream os = ctx.newTracePacket();
+
+                os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+
+                final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+                while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    if (pis.getFieldNumber() == (int) MESSAGES) {
+                        writeViewerConfigMessage(pis, os);
+                    }
+
+                    if (pis.getFieldNumber() == (int) GROUPS) {
+                        writeViewerConfigGroup(pis, os);
+                    }
+                }
+
+                os.end(outProtologViewerConfigToken);
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump to datasource", e);
+            }
+        });
+    }
+
+    private static void writeViewerConfigGroup(
+            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+        final long inGroupToken = pis.start(GROUPS);
+        final long outGroupToken = os.start(GROUPS);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) ID:
+                    int id = pis.readInt(ID);
+                    os.write(ID, id);
+                    break;
+                case (int) NAME:
+                    String name = pis.readString(NAME);
+                    os.write(NAME, name);
+                    break;
+                case (int) TAG:
+                    String tag = pis.readString(TAG);
+                    os.write(TAG, tag);
+                    break;
+                default:
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inGroupToken);
+        os.end(outGroupToken);
+    }
+
+    private static void writeViewerConfigMessage(
+            ProtoInputStream pis, ProtoOutputStream os) throws IOException {
+        final long inMessageToken = pis.start(MESSAGES);
+        final long outMessagesToken = os.start(MESSAGES);
+
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID:
+                    os.write(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID,
+                            pis.readLong(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID));
+                    break;
+                case (int) MESSAGE:
+                    os.write(MESSAGE, pis.readString(MESSAGE));
+                    break;
+                case (int) LEVEL:
+                    os.write(LEVEL, pis.readInt(LEVEL));
+                    break;
+                case (int) GROUP_ID:
+                    os.write(GROUP_ID, pis.readInt(GROUP_ID));
+                    break;
+                case (int) LOCATION:
+                    os.write(LOCATION, pis.readString(LOCATION));
+                    break;
+                default:
+                    throw new RuntimeException(
+                            "Unexpected field id " + pis.getFieldNumber());
+            }
+        }
+
+        pis.end(inMessageToken);
+        os.end(outMessagesToken);
+    }
+
+}
diff --git a/core/java/com/android/internal/protolog/common/LogLevel.java b/core/java/com/android/internal/protolog/common/LogLevel.java
index 16c34e1..b5541ae 100644
--- a/core/java/com/android/internal/protolog/common/LogLevel.java
+++ b/core/java/com/android/internal/protolog/common/LogLevel.java
@@ -17,10 +17,18 @@
 package com.android.internal.protolog.common;
 
 public enum LogLevel {
-    DEBUG("d"), VERBOSE("v"), INFO("i"), WARN("w"), ERROR("e"), WTF("wtf");
+    DEBUG("d", 1),
+    VERBOSE("v", 2),
+    INFO("i", 3),
+    WARN("w", 4),
+    ERROR("e", 5),
+    WTF("wtf", 6);
 
     public final String shortCode;
-    LogLevel(String shortCode) {
+    public final int id;
+
+    LogLevel(String shortCode, int id) {
         this.shortCode = shortCode;
+        this.id = id;
     }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a19b71c..d35c66e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4033,7 +4033,6 @@
     <!-- Allows an application to manage policy related to block package uninstallation.
         <p>Protection level: internal|role
         <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-        @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"
         android:protectionLevel="internal|role" />
@@ -4041,7 +4040,6 @@
     <!-- Allows an application to manage policy related to camera toggle.
         <p>Protection level: internal|role
         <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-        @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"
         android:protectionLevel="internal|role" />
@@ -4049,7 +4047,6 @@
     <!-- Allows an application to manage policy related to microphone toggle.
         <p>Protection level: internal|role
         <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-        @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"
         android:protectionLevel="internal|role" />
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 10aed8d..1429272 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,8 +16,6 @@
 
 package android.graphics;
 
-import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,7 +30,6 @@
 import android.graphics.fonts.SystemFonts;
 import android.graphics.text.PositionedGlyphs;
 import android.graphics.text.TextRunShaper;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.text.FontConfig;
@@ -931,7 +928,6 @@
         return String.format(xml, op, lang, font);
     }
 
-    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_prepend() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -947,7 +943,6 @@
         assertB3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_replace() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -963,7 +958,6 @@
         assertB3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_append() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -979,7 +973,6 @@
         assertA3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -995,7 +988,6 @@
         assertA3emFontIsUsed(typeface);
     }
 
-    @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
     @Test
     public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 9a55b80..880f30c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -580,6 +580,11 @@
         <permission name="android.permission.PREPARE_FACTORY_RESET" />
         <!-- Permission required for CTS test - FileIntegrityManagerTest -->
         <permission name="android.permission.SETUP_FSVERITY" />
+        <!-- Permissions required for CTS test - AppFunctionManagerTest -->
+        <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
+        <permission name="android.permission.EXECUTE_APP_FUNCTIONS" />
+        <!-- Permission required for CTS test - CtsNfcTestCases -->
+        <permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index ba5628c..b7bf055 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,10 +182,8 @@
 
             // For ignoring the customization, consume the new-locale-family element but don't
             // register any customizations.
-            if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
-                outCustomization.add(new FontConfig.Customization.LocaleFallback(
-                        Locale.forLanguageTag(lang), intOp, family));
-            }
+            outCustomization.add(new FontConfig.Customization.LocaleFallback(
+                    Locale.forLanguageTag(lang), intOp, family));
         } else {
             throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
         }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
new file mode 100644
index 0000000..35d459f
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.bar
+
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.Bubble
+import com.android.wm.shell.bubbles.BubbleData
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.BubbleTaskView
+import com.android.wm.shell.bubbles.BubbleTaskViewFactory
+import com.android.wm.shell.bubbles.DeviceConfig
+import com.android.wm.shell.bubbles.RegionSamplingProvider
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.handles.RegionSamplingHelper
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.util.Collections
+import java.util.concurrent.Executor
+
+/** Tests for [BubbleBarExpandedViewTest] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleBarExpandedViewTest {
+    companion object {
+        const val SCREEN_WIDTH = 2000
+        const val SCREEN_HEIGHT = 1000
+    }
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val windowManager = context.getSystemService(WindowManager::class.java)
+
+    private lateinit var mainExecutor: TestExecutor
+    private lateinit var bgExecutor: TestExecutor
+
+    private lateinit var expandedViewManager: BubbleExpandedViewManager
+    private lateinit var positioner: BubblePositioner
+    private lateinit var bubbleTaskView: BubbleTaskView
+
+    private lateinit var bubbleExpandedView: BubbleBarExpandedView
+    private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null
+    private var regionSamplingProvider: TestRegionSamplingProvider? = null
+
+    @Before
+    fun setUp() {
+        ProtoLog.REQUIRE_PROTOLOGTOOL = false
+        mainExecutor = TestExecutor()
+        bgExecutor = TestExecutor()
+        positioner = BubblePositioner(context, windowManager)
+        positioner.setShowingInBubbleBar(true)
+        val deviceConfig =
+            DeviceConfig(
+                windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
+                isLargeScreen = true,
+                isSmallTablet = false,
+                isLandscape = true,
+                isRtl = false,
+                insets = Insets.of(10, 20, 30, 40)
+            )
+        positioner.update(deviceConfig)
+
+        expandedViewManager = createExpandedViewManager()
+        bubbleTaskView = FakeBubbleTaskViewFactory().create()
+
+        val inflater = LayoutInflater.from(context)
+
+        regionSamplingProvider = TestRegionSamplingProvider()
+
+        bubbleExpandedView = (inflater.inflate(
+            R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
+        ) as BubbleBarExpandedView)
+        bubbleExpandedView.initialize(
+            expandedViewManager,
+            positioner,
+            false /* isOverflow */,
+            bubbleTaskView,
+            mainExecutor,
+            bgExecutor,
+            regionSamplingProvider
+        )
+
+        getInstrumentation().runOnMainSync(Runnable {
+            bubbleExpandedView.onAttachedToWindow()
+            // Helper should be created once attached to window
+            testableRegionSamplingHelper = regionSamplingProvider!!.helper
+        })
+    }
+
+    @After
+    fun tearDown() {
+        testableRegionSamplingHelper?.stopAndDestroy()
+    }
+
+    @Test
+    fun testCreateSamplingHelper_onAttach() {
+        assertThat(testableRegionSamplingHelper).isNotNull()
+    }
+
+    @Test
+    fun testDestroySamplingHelper_onDetach() {
+        bubbleExpandedView.onDetachedFromWindow()
+        assertThat(testableRegionSamplingHelper!!.isDestroyed).isTrue()
+    }
+
+    @Test
+    fun testStopSampling_onDragStart() {
+        bubbleExpandedView.setContentVisibility(true)
+        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
+
+        bubbleExpandedView.setDragging(true)
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+    }
+
+    @Test
+    fun testStartSampling_onDragEnd() {
+        bubbleExpandedView.setDragging(true)
+        bubbleExpandedView.setContentVisibility(true)
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+
+        bubbleExpandedView.setDragging(false)
+        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
+    }
+
+    @Test
+    fun testStartSampling_onContentVisible() {
+        bubbleExpandedView.setContentVisibility(true)
+        assertThat(testableRegionSamplingHelper!!.setWindowVisible).isTrue()
+        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
+    }
+
+    @Test
+    fun testStopSampling_onContentInvisible() {
+        bubbleExpandedView.setContentVisibility(false)
+
+        assertThat(testableRegionSamplingHelper!!.setWindowInvisible).isTrue()
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+    }
+
+    @Test
+    fun testSampling_startStopAnimating_visible() {
+        bubbleExpandedView.isAnimating = true
+        bubbleExpandedView.setContentVisibility(true)
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+
+        bubbleExpandedView.isAnimating = false
+        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
+    }
+
+    @Test
+    fun testSampling_startStopAnimating_invisible() {
+        bubbleExpandedView.isAnimating = true
+        bubbleExpandedView.setContentVisibility(false)
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+        testableRegionSamplingHelper!!.reset()
+
+        bubbleExpandedView.isAnimating = false
+        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
+    }
+
+    private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
+        override fun create(): BubbleTaskView {
+            val taskViewTaskController = mock<TaskViewTaskController>()
+            val taskView = TaskView(context, taskViewTaskController)
+            val taskInfo = mock<ActivityManager.RunningTaskInfo>()
+            whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
+            return BubbleTaskView(taskView, mainExecutor)
+        }
+    }
+
+    private inner class TestRegionSamplingProvider : RegionSamplingProvider {
+
+        lateinit var helper: TestableRegionSamplingHelper
+
+        override fun createHelper(
+            sampledView: View?,
+            callback: RegionSamplingHelper.SamplingCallback?,
+            backgroundExecutor: Executor?,
+            mainExecutor: Executor?
+        ): RegionSamplingHelper {
+            helper = TestableRegionSamplingHelper(sampledView, callback, backgroundExecutor,
+                mainExecutor)
+            return helper
+        }
+    }
+
+    private inner class TestableRegionSamplingHelper(
+        sampledView: View?,
+        samplingCallback: SamplingCallback?,
+        backgroundExecutor: Executor?,
+        mainExecutor: Executor?
+    ) : RegionSamplingHelper(sampledView, samplingCallback, backgroundExecutor, mainExecutor) {
+
+        var isStarted = false
+        var isStopped = false
+        var isDestroyed = false
+        var setWindowVisible = false
+        var setWindowInvisible = false
+
+        override fun start(initialSamplingBounds: Rect) {
+            super.start(initialSamplingBounds)
+            isStarted = true
+        }
+
+        override fun stop() {
+            super.stop()
+            isStopped = true
+        }
+
+        override fun stopAndDestroy() {
+            super.stopAndDestroy()
+            isDestroyed = true
+        }
+
+        override fun setWindowVisible(visible: Boolean) {
+            super.setWindowVisible(visible)
+            if (visible) {
+                setWindowVisible = true
+            } else {
+                setWindowInvisible = true
+            }
+        }
+
+        fun reset() {
+            isStarted = false
+            isStopped = false
+            isDestroyed = false
+            setWindowVisible = false
+            setWindowInvisible = false
+        }
+    }
+
+    private fun createExpandedViewManager(): BubbleExpandedViewManager {
+        return object : BubbleExpandedViewManager {
+            override val overflowBubbles: List<Bubble>
+                get() = Collections.emptyList()
+
+            override fun setOverflowListener(listener: BubbleData.Listener) {
+            }
+
+            override fun collapseStack() {
+            }
+
+            override fun updateWindowFlagsForBackpress(intercept: Boolean) {
+            }
+
+            override fun promoteBubbleFromOverflow(bubble: Bubble) {
+            }
+
+            override fun removeBubble(key: String, reason: Int) {
+            }
+
+            override fun dismissBubble(bubble: Bubble, reason: Int) {
+            }
+
+            override fun setAppBubbleTaskId(key: String, taskId: Int) {
+            }
+
+            override fun isStackExpanded(): Boolean {
+                return true
+            }
+
+            override fun isShowingAsBubbleBar(): Boolean {
+                return true
+            }
+
+            override fun hideCurrentInputMethod() {
+            }
+
+            override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+            }
+        }
+    }
+
+    private class TestExecutor : ShellExecutor {
+
+        private val runnables: MutableList<Runnable> = mutableListOf()
+
+        override fun execute(runnable: Runnable) {
+            runnables.add(runnable)
+        }
+
+        override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+            execute(runnable)
+        }
+
+        override fun removeCallbacks(runnable: Runnable?) {}
+
+        override fun hasCallback(runnable: Runnable?): Boolean = false
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
index b92b8ef..a06cf78 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
@@ -329,7 +329,7 @@
         /**
          * Get the sampled region of interest from the sampled view
          * @param sampledView The view that this helper is attached to for convenience
-         * @return the region to be sampled in sceen coordinates. Return {@code null} to avoid
+         * @return the region to be sampled in screen coordinates. Return {@code null} to avoid
          * sampling in this frame
          */
         Rect getSampledRegion(View sampledView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
new file mode 100644
index 0000000..05ce361
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("AppToWebUtils")
+
+package com.android.wm.shell.apptoweb
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+
+private val browserIntent = Intent()
+    .setAction(Intent.ACTION_VIEW)
+    .addCategory(Intent.CATEGORY_BROWSABLE)
+    .setData(Uri.parse("http:"))
+
+/**
+ * Returns a boolean indicating whether a given package is a browser app.
+ */
+fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean {
+    browserIntent.setPackage(packageName)
+    val list = context.packageManager.queryIntentActivitiesAsUser(
+        browserIntent, PackageManager.MATCH_ALL, userId
+    )
+
+    list.forEach {
+        if (it.activityInfo != null && it.handleAllWebDataURI) {
+            return true
+        }
+    }
+    return false
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt
new file mode 100644
index 0000000..249185e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.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.wm.shell.apptoweb
+
+import android.app.ActivityTaskManager
+import android.app.IActivityTaskManager
+import android.app.IAssistDataReceiver
+import android.app.assist.AssistContent
+import android.content.Context
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.os.RemoteException
+import android.util.Slog
+import java.lang.ref.WeakReference
+import java.util.Collections
+import java.util.WeakHashMap
+import java.util.concurrent.Executor
+
+/**
+ * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
+ * if provided from the task.
+ */
+class AssistContentRequester(
+    context: Context,
+    private val callBackExecutor: Executor,
+    private val systemInteractionExecutor: Executor
+) {
+    interface Callback {
+        // Called when the [AssistContent] of the requested task is available.
+        fun onAssistContentAvailable(assistContent: AssistContent?)
+    }
+
+    private val activityTaskManager: IActivityTaskManager = ActivityTaskManager.getService()
+    private val attributionTag: String? = context.attributionTag
+    private val packageName: String = context.applicationContext.packageName
+
+    // If system loses the callback, our internal cache of original callback will also get cleared.
+    private val pendingCallbacks = Collections.synchronizedMap(WeakHashMap<Any, Callback>())
+
+    /**
+     * Request the [AssistContent] from the task with the provided id.
+     *
+     * @param taskId to query for the content.
+     * @param callback to call when the content is available, called on the main thread.
+     */
+    fun requestAssistContent(taskId: Int, callback: Callback) {
+        // ActivityTaskManager interaction here is synchronous, so call off the main thread.
+        systemInteractionExecutor.execute {
+            try {
+                val success = activityTaskManager.requestAssistDataForTask(
+                    AssistDataReceiver(callback, this),
+                    taskId,
+                    packageName,
+                    attributionTag,
+                    false /* fetchStructure */
+                )
+                if (!success) {
+                    executeOnMainExecutor { callback.onAssistContentAvailable(null) }
+                }
+            } catch (e: RemoteException) {
+                Slog.e(TAG, "Requesting assist content failed for task: $taskId", e)
+            }
+        }
+    }
+
+    private fun executeOnMainExecutor(callback: Runnable) {
+        callBackExecutor.execute(callback)
+    }
+
+    private class AssistDataReceiver(
+            callback: Callback,
+            parent: AssistContentRequester
+    ) : IAssistDataReceiver.Stub() {
+        // The AssistDataReceiver binder callback object is passed to a system server, that may
+        // keep hold of it for longer than the lifetime of the AssistContentRequester object,
+        // potentially causing a memory leak. In the callback passed to the system server, only
+        // keep a weak reference to the parent object and lookup its callback if it still exists.
+        private val parentRef: WeakReference<AssistContentRequester>
+        private val callbackKey = Any()
+
+        init {
+            parent.pendingCallbacks[callbackKey] = callback
+            parentRef = WeakReference(parent)
+        }
+
+        override fun onHandleAssistData(data: Bundle?) {
+            val content = data?.getParcelable(ASSIST_KEY_CONTENT, AssistContent::class.java)
+            if (content == null) {
+                Slog.d(TAG, "Received AssistData, but no AssistContent found")
+                return
+            }
+            val requester = parentRef.get()
+            if (requester != null) {
+                val callback = requester.pendingCallbacks[callbackKey]
+                if (callback != null) {
+                    requester.executeOnMainExecutor { callback.onAssistContentAvailable(content) }
+                } else {
+                    Slog.d(TAG, "Callback received after calling UI was disposed of")
+                }
+            } else {
+                Slog.d(TAG, "Callback received after Requester was collected")
+            }
+        }
+
+        override fun onHandleAssistScreenshot(screenshot: Bitmap) {}
+    }
+
+    companion object {
+        private const val TAG = "AssistContentRequester"
+        private const val ASSIST_KEY_CONTENT = "content"
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
index bfe1306a..6207e5b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
@@ -1,6 +1,8 @@
 atsjenk@google.com
 jorgegil@google.com
 madym@google.com
+mattsziklay@google.com
+mdehaini@google.com
 pbdr@google.com
 tkachenkoi@google.com
 vaniadesmonda@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 0c95934..169361a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -609,7 +609,8 @@
                             callback.onBubbleViewsReady(bubble);
                         }
                     },
-                    mMainExecutor);
+                    mMainExecutor,
+                    mBgExecutor);
             if (mInflateSynchronously) {
                 mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground());
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index f32974e..68c4657 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -80,7 +80,10 @@
                 expandedViewManager,
                 positioner,
                 /* isOverflow= */ true,
-                /* bubbleTaskView= */ null
+                /* bubbleTaskView= */ null,
+                /* mainExecutor= */ null,
+                /* backgroundExecutor= */ null,
+                /* regionSamplingProvider= */ null
             )
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5f8f0fd..0c0fd7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -60,6 +60,9 @@
 
         /** Called when back is pressed on the task root. */
         void onBackPressed();
+
+        /** Called when task removal has started. */
+        void onTaskRemovalStarted();
     }
 
     private final Context mContext;
@@ -190,6 +193,7 @@
                 ((ViewGroup) mParentView).removeView(mTaskView);
                 mTaskView = null;
             }
+            mListener.onTaskRemovalStarted();
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 13855f7..3982a23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 import android.util.PathParser;
 import android.view.LayoutInflater;
+import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -47,6 +48,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
@@ -222,7 +224,16 @@
                 ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s",
                         mBubble.getKey());
                 viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(),
-                        mPositioner.get(), false /* isOverflow */, viewInfo.taskView);
+                        mPositioner.get(), false /* isOverflow */, viewInfo.taskView,
+                        mMainExecutor, mBgExecutor, new RegionSamplingProvider() {
+                            @Override
+                            public RegionSamplingHelper createHelper(View sampledView,
+                                    RegionSamplingHelper.SamplingCallback callback,
+                                    Executor backgroundExecutor, Executor mainExecutor) {
+                                return RegionSamplingProvider.super.createHelper(sampledView,
+                                        callback, backgroundExecutor, mainExecutor);
+                            }
+                        });
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
index 5cfebf8..1b7bb0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 import android.util.PathParser;
 import android.view.LayoutInflater;
+import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -46,6 +47,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
@@ -85,6 +87,7 @@
     private boolean mSkipInflation;
     private Callback mCallback;
     private Executor mMainExecutor;
+    private Executor mBackgroundExecutor;
 
     /**
      * Creates a task to load information for the provided {@link Bubble}. Once all info
@@ -100,7 +103,8 @@
             BubbleIconFactory factory,
             boolean skipInflation,
             Callback c,
-            Executor mainExecutor) {
+            Executor mainExecutor,
+            Executor backgroundExecutor) {
         mBubble = b;
         mContext = new WeakReference<>(context);
         mExpandedViewManager = new WeakReference<>(expandedViewManager);
@@ -112,6 +116,7 @@
         mSkipInflation = skipInflation;
         mCallback = c;
         mMainExecutor = mainExecutor;
+        mBackgroundExecutor = backgroundExecutor;
     }
 
     @Override
@@ -123,7 +128,7 @@
         if (mLayerView.get() != null) {
             return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
                     mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
-                    mBubble, mSkipInflation);
+                    mBubble, mSkipInflation, mMainExecutor, mBackgroundExecutor);
         } else {
             return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
                     mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
@@ -188,7 +193,9 @@
                 BubbleBarLayerView layerView,
                 BubbleIconFactory iconFactory,
                 Bubble b,
-                boolean skipInflation) {
+                boolean skipInflation,
+                Executor mainExecutor,
+                Executor backgroundExecutor) {
             BubbleViewInfo info = new BubbleViewInfo();
 
             if (!skipInflation && !b.isInflated()) {
@@ -197,7 +204,16 @@
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
                 info.bubbleBarExpandedView.initialize(
-                        expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
+                        expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView,
+                        mainExecutor, backgroundExecutor, new RegionSamplingProvider() {
+                            @Override
+                            public RegionSamplingHelper createHelper(View sampledView,
+                                    RegionSamplingHelper.SamplingCallback callback,
+                                    Executor backgroundExecutor, Executor mainExecutor) {
+                                return RegionSamplingProvider.super.createHelper(sampledView,
+                                        callback, backgroundExecutor, mainExecutor);
+                            }
+                        });
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java
new file mode 100644
index 0000000..30f5c8fd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java
@@ -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.wm.shell.bubbles;
+
+import android.view.View;
+
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper to provide a {@link com.android.wm.shell.shared.handles.RegionSamplingHelper} to allow
+ * testing it.
+ */
+public interface RegionSamplingProvider {
+
+    /** Creates and returns the region sampling helper */
+    default RegionSamplingHelper createHelper(View sampledView,
+            RegionSamplingHelper.SamplingCallback callback,
+            Executor backgroundExecutor,
+            Executor mainExecutor) {
+        return new RegionSamplingHelper(sampledView,
+                callback, backgroundExecutor, mainExecutor);
+    }
+
+}
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 565fde0..74c3748 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
@@ -253,6 +253,7 @@
             return;
         }
         setDragPivot(bbev);
+        bbev.setDragging(true);
         // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
         final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
 
@@ -329,6 +330,7 @@
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 bbev.resetPivot();
+                bbev.setDragging(false);
             }
         });
         startNewDragAnimation(animatorSet);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index f90b2aa..ec235a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -19,10 +19,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.Rect;
@@ -37,6 +34,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
@@ -46,9 +44,12 @@
 import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.bubbles.RegionSamplingProvider;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 import com.android.wm.shell.taskview.TaskView;
 
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 /** Expanded view of a bubble when it's part of the bubble bar. */
@@ -92,16 +93,35 @@
     private boolean mIsOverflow;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
     private BubbleBarMenuViewController mMenuViewController;
-    private @Nullable Supplier<Rect> mLayerBoundsSupplier;
-    private @Nullable Listener mListener;
+    @Nullable
+    private Supplier<Rect> mLayerBoundsSupplier;
+    @Nullable
+    private Listener mListener;
 
     private BubbleBarHandleView mHandleView;
-    private @Nullable TaskView mTaskView;
-    private @Nullable BubbleOverflowContainerView mOverflowView;
+    @Nullable
+    private TaskView mTaskView;
+    @Nullable
+    private BubbleOverflowContainerView mOverflowView;
 
+    /**
+     * The handle shown in the caption area is tinted based on the background color of the area.
+     * This can vary so we sample the caption region and update the handle color based on that.
+     * If we're showing the overflow, the helper and executors will be null.
+     */
+    @Nullable
+    private RegionSamplingHelper mRegionSamplingHelper;
+    @Nullable
+    private RegionSamplingProvider mRegionSamplingProvider;
+    @Nullable
+    private Executor mMainExecutor;
+    @Nullable
+    private Executor mBackgroundExecutor;
+    private final Rect mSampleRect = new Rect();
+    private final int[] mLoc = new int[2];
+
+    /** Height of the caption inset at the top of the TaskView */
     private int mCaptionHeight;
-
-    private int mBackgroundColor;
     /** Corner radius used when view is resting */
     private float mRestingCornerRadius = 0f;
     /** Corner radius applied while dragging */
@@ -116,6 +136,7 @@
      */
     private boolean mIsContentVisible = false;
     private boolean mIsAnimating;
+    private boolean mIsDragging;
 
     public BubbleBarExpandedView(Context context) {
         this(context, null);
@@ -154,21 +175,20 @@
         setOnTouchListener((v, event) -> true);
     }
 
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        // Hide manage menu when view disappears
-        mMenuViewController.hideMenu(false /* animated */);
-    }
-
     /** Initializes the view, must be called before doing anything else. */
     public void initialize(BubbleExpandedViewManager expandedViewManager,
             BubblePositioner positioner,
             boolean isOverflow,
-            @Nullable BubbleTaskView bubbleTaskView) {
+            @Nullable BubbleTaskView bubbleTaskView,
+            @Nullable Executor mainExecutor,
+            @Nullable Executor backgroundExecutor,
+            @Nullable RegionSamplingProvider regionSamplingProvider) {
         mManager = expandedViewManager;
         mPositioner = positioner;
         mIsOverflow = isOverflow;
+        mMainExecutor = mainExecutor;
+        mBackgroundExecutor = backgroundExecutor;
+        mRegionSamplingProvider = regionSamplingProvider;
 
         if (mIsOverflow) {
             mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
@@ -191,6 +211,7 @@
             mTaskView.setEnableSurfaceClipping(true);
             mTaskView.setCornerRadius(mCurrentCornerRadius);
             mTaskView.setVisibility(VISIBLE);
+            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
 
             // Handle view needs to draw on top of task view.
             bringChildToFront(mHandleView);
@@ -245,32 +266,40 @@
         return mHandleView;
     }
 
-    // TODO (b/275087636): call this when theme/config changes
     /** Updates the view based on the current theme. */
     public void applyThemeAttrs() {
+        mCaptionHeight = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
         mRestingCornerRadius = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_corner_radius
-        );
+                R.dimen.bubble_bar_expanded_view_corner_radius);
         mDraggedCornerRadius = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_corner_radius_dragged
-        );
+                R.dimen.bubble_bar_expanded_view_corner_radius_dragged);
 
         mCurrentCornerRadius = mRestingCornerRadius;
 
-        final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
-                android.R.attr.colorBackgroundFloating});
-        mBackgroundColor = ta.getColor(0, Color.WHITE);
-        ta.recycle();
-        mCaptionHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_caption_height);
-
         if (mTaskView != null) {
             mTaskView.setCornerRadius(mCurrentCornerRadius);
-            updateHandleColor(true /* animated */);
+            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
         }
     }
 
     @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Hide manage menu when view disappears
+        mMenuViewController.hideMenu(false /* animated */);
+        if (mRegionSamplingHelper != null) {
+            mRegionSamplingHelper.stopAndDestroy();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        recreateRegionSamplingHelper();
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         if (mTaskView != null) {
@@ -284,16 +313,13 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         if (mTaskView != null) {
-            mTaskView.layout(l, t, r,
-                    t + mTaskView.getMeasuredHeight());
-            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
+            mTaskView.layout(l, t, r, t + mTaskView.getMeasuredHeight());
         }
     }
 
     @Override
     public void onTaskCreated() {
         setContentVisibility(true);
-        updateHandleColor(false /* animated */);
         if (mListener != null) {
             mListener.onTaskCreated();
         }
@@ -305,11 +331,70 @@
     }
 
     @Override
+    public void onTaskRemovalStarted() {
+        if (mRegionSamplingHelper != null) {
+            mRegionSamplingHelper.stopAndDestroy();
+        }
+    }
+
+    @Override
     public void onBackPressed() {
         if (mListener == null) return;
         mListener.onBackPressed();
     }
 
+    /**
+     * Set whether this view is currently being dragged.
+     *
+     * When dragging, the handle is hidden and content shouldn't be sampled. When dragging has
+     * ended we should start again.
+     */
+    public void setDragging(boolean isDragging) {
+        if (isDragging != mIsDragging) {
+            mIsDragging = isDragging;
+            updateSamplingState();
+        }
+    }
+
+    /** Returns whether region sampling should be enabled, i.e. if task view content is visible. */
+    private boolean shouldSampleRegion() {
+        return mTaskView != null
+                && mTaskView.getTaskInfo() != null
+                && !mIsDragging
+                && !mIsAnimating
+                && mIsContentVisible;
+    }
+
+    /**
+     * Handles starting or stopping the region sampling helper based on
+     * {@link #shouldSampleRegion()}.
+     */
+    private void updateSamplingState() {
+        if (mRegionSamplingHelper == null) return;
+        boolean shouldSample = shouldSampleRegion();
+        if (shouldSample) {
+            mRegionSamplingHelper.start(getCaptionSampleRect());
+        } else {
+            mRegionSamplingHelper.stop();
+        }
+    }
+
+    /** Returns the current area of the caption bar, in screen coordinates. */
+    Rect getCaptionSampleRect() {
+        if (mTaskView == null) return null;
+        mTaskView.getLocationOnScreen(mLoc);
+        mSampleRect.set(mLoc[0], mLoc[1],
+                mLoc[0] + mTaskView.getWidth(),
+                mLoc[1] + mCaptionHeight);
+        return mSampleRect;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    public RegionSamplingHelper getRegionSamplingHelper() {
+        return mRegionSamplingHelper;
+    }
+
     /** Cleans up the expanded view, should be called when the bubble is no longer active. */
     public void cleanUpExpandedState() {
         mMenuViewController.hideMenu(false /* animated */);
@@ -394,27 +479,14 @@
 
         if (!mIsAnimating) {
             mTaskView.setAlpha(visible ? 1f : 0f);
+            if (mRegionSamplingHelper != null) {
+                mRegionSamplingHelper.setWindowVisible(visible);
+            }
+            updateSamplingState();
         }
     }
 
     /**
-     * Updates the handle color based on the task view status bar or background color; if those
-     * are transparent it defaults to the background color pulled from system theme attributes.
-     */
-    private void updateHandleColor(boolean animated) {
-        if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
-        int color = mBackgroundColor;
-        ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
-        if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
-            color = taskDescription.getStatusBarColor();
-        } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
-            color = taskDescription.getBackgroundColor();
-        }
-        final boolean isRegionDark = Color.luminance(color) <= 0.5;
-        mHandleView.updateHandleColor(isRegionDark, animated);
-    }
-
-    /**
      * Sets the alpha of both this view and the task view.
      */
     public void setTaskViewAlpha(float alpha) {
@@ -442,6 +514,11 @@
      */
     public void setAnimating(boolean animating) {
         mIsAnimating = animating;
+        if (mIsAnimating) {
+            // Stop sampling while animating -- when animating is done setContentVisibility will
+            // re-trigger sampling if we're visible.
+            updateSamplingState();
+        }
         // If we're done animating, apply the correct visibility.
         if (!animating) {
             setContentVisibility(mIsContentVisible);
@@ -481,6 +558,37 @@
         }
     }
 
+    private void recreateRegionSamplingHelper() {
+        if (mRegionSamplingHelper != null) {
+            mRegionSamplingHelper.stopAndDestroy();
+        }
+        if (mMainExecutor == null || mBackgroundExecutor == null
+                || mRegionSamplingProvider == null) {
+            // Null when it's the overflow / don't need sampling then.
+            return;
+        }
+        mRegionSamplingHelper = mRegionSamplingProvider.createHelper(this,
+                new RegionSamplingHelper.SamplingCallback() {
+                    @Override
+                    public void onRegionDarknessChanged(boolean isRegionDark) {
+                        if (mHandleView != null) {
+                            mHandleView.updateHandleColor(isRegionDark,
+                                    true /* animated */);
+                        }
+                    }
+
+                    @Override
+                    public Rect getSampledRegion(View sampledView) {
+                        return getCaptionSampleRect();
+                    }
+
+                    @Override
+                    public boolean isSamplingEnabled() {
+                        return shouldSampleRegion();
+                    }
+                }, mMainExecutor, mBackgroundExecutor);
+    }
+
     private class HandleViewAccessibilityDelegate extends AccessibilityDelegate {
         @Override
         public void onInitializeAccessibilityNodeInfo(@NonNull View host,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index c91567d..e781c07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -42,7 +42,9 @@
 
     private final @ColorInt int mHandleLightColor;
     private final @ColorInt int mHandleDarkColor;
-    private @Nullable ObjectAnimator mColorChangeAnim;
+    private @ColorInt int mCurrentColor;
+    @Nullable
+    private ObjectAnimator mColorChangeAnim;
 
     public BubbleBarHandleView(Context context) {
         this(context, null /* attrs */);
@@ -88,13 +90,17 @@
      *
      * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
      *                     should be light (and vice versa).
-     * @param animated      Whether to animate the change, or apply it immediately.
+     * @param animated     Whether to animate the change, or apply it immediately.
      */
     public void updateHandleColor(boolean isRegionDark, boolean animated) {
         int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+        if (newColor == mCurrentColor) {
+            return;
+        }
         if (mColorChangeAnim != null) {
             mColorChangeAnim.cancel();
         }
+        mCurrentColor = newColor;
         if (animated) {
             mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
             mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
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 02ecfd9..7054c17c 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
@@ -38,6 +38,7 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+import com.android.wm.shell.apptoweb.AssistContentRequester;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -240,6 +241,7 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
+            AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
@@ -263,6 +265,7 @@
                     rootTaskDisplayAreaOrganizer,
                     interactionJankMonitor,
                     genericLinksParser,
+                    assistContentRequester,
                     multiInstanceHelper,
                     desktopTasksLimiter,
                     desktopActivityOrientationHandler);
@@ -291,6 +294,15 @@
         return new AppToWebGenericLinksParser(context, mainExecutor);
     }
 
+    @Provides
+    static AssistContentRequester provideAssistContentRequester(
+            Context context,
+            @ShellMainThread ShellExecutor shellExecutor,
+            @ShellBackgroundThread ShellExecutor bgExecutor
+    ) {
+        return new AssistContentRequester(context, shellExecutor, bgExecutor);
+    }
+
     //
     // Freeform
     //
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
deleted file mode 100644
index 1dad413..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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.ProtoLog;
-import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-
-import java.util.Optional;
-
-/**
- * Main stage for split-screen mode. When split-screen is active all standard activity types launch
- * on the main stage, except for task that are explicitly pinned to the {@link StageTaskListener}.
- * @see StageCoordinator
- */
-class MainStage extends StageTaskListener {
-    private boolean mIsActive = false;
-
-    MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
-            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider,
-            Optional<WindowDecorViewModel> windowDecorViewModel) {
-        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider, windowDecorViewModel);
-    }
-
-    boolean isActive() {
-        return mIsActive;
-    }
-
-    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);
-        }
-
-        mIsActive = true;
-    }
-
-    void deactivate(WindowContainerTransaction wct) {
-        deactivate(wct, false /* toTop */);
-    }
-
-    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;
-        final WindowContainerToken rootToken = mRootTaskInfo.token;
-        wct.reparentTasks(
-                rootToken,
-                null /* newParent */,
-                null /* windowingModes */,
-                null /* activityTypes */,
-                toTop);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 526c1d4..b36b1f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -44,7 +44,7 @@
     int STAGE_TYPE_UNDEFINED = -1;
     /**
      * The main stage type.
-     * @see MainStage
+     * @see StageTaskListener
      */
     int STAGE_TYPE_MAIN = 0;
 
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 f3959cc..f3113dc 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
@@ -34,6 +34,7 @@
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
@@ -154,14 +155,12 @@
 import java.util.concurrent.Executor;
 
 /**
- * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
- * other stages.
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen stages.
  * Some high-level rules:
  * - The {@link StageCoordinator} is only considered active if the other stages contain at
  * least one child task.
- * - The {@link MainStage} should only have children if the coordinator is active.
- * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
- * and other stages are visible.
+ * - The {@link SplitLayout} divider is only visible if multiple {@link StageTaskListener}s are
+ * visible
  * - Both stages are put under a single-top root task.
  * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
  * {@link #onStageHasChildrenChanged(StageListenerImpl).}
@@ -174,7 +173,7 @@
 
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
 
-    private final MainStage mMainStage;
+    private final StageTaskListener mMainStage;
     private final StageListenerImpl mMainStageListener = new StageListenerImpl();
     private final StageTaskListener mSideStage;
     private final StageListenerImpl mSideStageListener = new StageListenerImpl();
@@ -329,7 +328,7 @@
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
 
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task");
-        mMainStage = new MainStage(
+        mMainStage = new StageTaskListener(
                 mContext,
                 mTaskOrganizer,
                 mDisplayId,
@@ -367,8 +366,9 @@
 
     @VisibleForTesting
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-            ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage,
-            DisplayController displayController, DisplayImeController displayImeController,
+            ShellTaskOrganizer taskOrganizer, StageTaskListener mainStage,
+            StageTaskListener sideStage, DisplayController displayController,
+            DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
             Handler mainHandler, Optional<RecentTasksController> recentTasks,
@@ -420,6 +420,10 @@
         return mSideStageListener.mVisible && mMainStageListener.mVisible;
     }
 
+    private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) {
+        mMainStage.activate(wct, includingTopTask);
+    }
+
     public boolean isSplitActive() {
         return mMainStage.isActive();
     }
@@ -505,10 +509,10 @@
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        /**
-         * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
-         * other stages no longer have children.
-         */
+
+        // MainStage will be deactivated in onStageHasChildrenChanged() if the other stages
+        // no longer have children.
+
         final boolean result = mSideStage.removeTask(taskId,
                 isSplitActive() ? mMainStage.mRootTaskInfo.token : null,
                 wct);
@@ -805,7 +809,7 @@
         if (!isSplitActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
-            mMainStage.activate(wct, false /* reparent */);
+            activateSplit(wct, false /* reparentToTop */);
         }
         mSplitLayout.setDivideRatio(snapPosition);
         updateWindowBounds(mSplitLayout, wct);
@@ -872,7 +876,7 @@
         if (!isSplitActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
-            mMainStage.activate(wct, false /* reparent */);
+            activateSplit(wct, false /* reparentToTop */);
         }
 
         setSideStagePosition(splitPosition, wct);
@@ -1439,7 +1443,7 @@
             setSideStagePosition(startPosition, wct);
             mSideStage.addTask(taskInfo, wct);
         }
-        mMainStage.activate(wct, true /* includingTopTask */);
+        activateSplit(wct, true /* reparentToTop */);
         prepareSplitLayout(wct, resizeAnim);
     }
 
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 4593553..29b5114 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
@@ -72,6 +72,10 @@
 public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = StageTaskListener.class.getSimpleName();
 
+    // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the
+    // stages should have this be set/being used
+    private boolean mIsActive;
+
     /** Callback interface for listening to changes in a split-screen stage. */
     public interface StageListenerCallbacks {
         void onRootTaskAppeared();
@@ -475,6 +479,44 @@
         });
     }
 
+    // ---------
+    // Previously only used in MainStage
+    boolean isActive() {
+        return mIsActive;
+    }
+
+    void activate(WindowContainerTransaction wct, boolean includingTopTask) {
+        if (mIsActive) return;
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b",
+                includingTopTask);
+
+        if (includingTopTask) {
+            reparentTopTask(wct);
+        }
+
+        mIsActive = true;
+    }
+
+    void deactivate(WindowContainerTransaction wct) {
+        deactivate(wct, false /* toTop */);
+    }
+
+    void deactivate(WindowContainerTransaction wct, boolean toTop) {
+        if (!mIsActive) return;
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: toTop=%b rootTaskInfo=%s",
+                toTop, mRootTaskInfo);
+        mIsActive = false;
+
+        if (mRootTaskInfo == null) return;
+        final WindowContainerToken rootToken = mRootTaskInfo.token;
+        wct.reparentTasks(
+                rootToken,
+                null /* newParent */,
+                null /* windowingModes */,
+                null /* activityTypes */,
+                toTop);
+    }
+
     // --------
     // Previously only used in SideStage
     boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
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 c88c1e2..7919068 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
@@ -90,6 +90,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+import com.android.wm.shell.apptoweb.AssistContentRequester;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -182,6 +183,7 @@
     private final Region mExclusionRegion = Region.obtain();
     private boolean mInImmersiveMode;
     private final String mSysUIPackageName;
+    private final AssistContentRequester mAssistContentRequester;
 
     private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
     private final ISystemGestureExclusionListener mGestureExclusionListener =
@@ -217,6 +219,7 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
+            AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler
@@ -238,6 +241,7 @@
                 transitions,
                 desktopTasksController,
                 genericLinksParser,
+                assistContentRequester,
                 multiInstanceHelper,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
@@ -267,6 +271,7 @@
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
             AppToWebGenericLinksParser genericLinksParser,
+            AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
@@ -304,6 +309,7 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mDesktopTasksLimiter = desktopTasksLimiter;
         mActivityOrientationChangeHandler = activityOrientationChangeHandler;
+        mAssistContentRequester = assistContentRequester;
         mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
             DesktopModeWindowDecoration decoration;
             RunningTaskInfo taskInfo;
@@ -626,7 +632,7 @@
             } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
                 if (!decoration.isHandleMenuActive()) {
                     moveTaskToFront(decoration.mTaskInfo);
-                    decoration.createHandleMenu(mSplitScreenController);
+                    decoration.createHandleMenu();
                 }
             } else if (id == R.id.maximize_window) {
                 // TODO(b/346441962): move click detection logic into the decor's
@@ -1270,6 +1276,7 @@
                         mSyncQueue,
                         mRootTaskDisplayAreaOrganizer,
                         mGenericLinksParser,
+                        mAssistContentRequester,
                         mMultiInstanceHelper);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
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 aa43c8d..142be91 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
@@ -38,6 +38,7 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
+import android.app.assist.AssistContent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -75,6 +76,8 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+import com.android.wm.shell.apptoweb.AppToWebUtils;
+import com.android.wm.shell.apptoweb.AssistContentRequester;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.MultiInstanceHelper;
@@ -150,6 +153,7 @@
     private CharSequence mAppName;
     private CapturedLink mCapturedLink;
     private Uri mGenericLink;
+    private Uri mWebUri;
     private Consumer<Uri> mOpenInBrowserClickListener;
 
     private ExclusionRegionListener mExclusionRegionListener;
@@ -158,6 +162,7 @@
     private final MaximizeMenuFactory mMaximizeMenuFactory;
     private final HandleMenuFactory mHandleMenuFactory;
     private final AppToWebGenericLinksParser mGenericLinksParser;
+    private final AssistContentRequester mAssistContentRequester;
 
     // Hover state for the maximize menu and button. The menu will remain open as long as either of
     // these is true. See {@link #onMaximizeHoverStateChanged()}.
@@ -184,16 +189,16 @@
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             AppToWebGenericLinksParser genericLinksParser,
+            AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper) {
         this (context, userContext, displayController, splitScreenController, taskOrganizer,
                 taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
-                rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
-                SurfaceControl.Transaction::new,  WindowContainerTransaction::new,
-                SurfaceControl::new, new WindowManagerWrapper(
+                rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
+                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+                WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
                         context.getSystemService(WindowManager.class)),
-                new SurfaceControlViewHostFactory() {},
-                DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE,
-                multiInstanceHelper);
+                new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE,
+                DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper);
     }
 
     DesktopModeWindowDecoration(
@@ -210,6 +215,7 @@
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             AppToWebGenericLinksParser genericLinksParser,
+            AssistContentRequester assistContentRequester,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -230,6 +236,7 @@
         mSyncQueue = syncQueue;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mGenericLinksParser = genericLinksParser;
+        mAssistContentRequester = assistContentRequester;
         mMaximizeMenuFactory = maximizeMenuFactory;
         mHandleMenuFactory = handleMenuFactory;
         mMultiInstanceHelper = multiInstanceHelper;
@@ -478,10 +485,18 @@
 
     @Nullable
     private Uri getBrowserLink() {
+        // Do not show browser link in browser applications
+        final ComponentName baseActivity = mTaskInfo.baseActivity;
+        if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
+                baseActivity.getPackageName(), mUserContext.getUserId())) {
+            return null;
+        }
         // If the captured link is available and has not expired, return the captured link.
         // Otherwise, return the generic link which is set to null if a generic link is unavailable.
         if (mCapturedLink != null && !mCapturedLink.mExpired) {
             return mCapturedLink.mUri;
+        } else if (mWebUri != null) {
+            return mWebUri;
         }
         return mGenericLink;
     }
@@ -987,18 +1002,32 @@
     }
 
     /**
-     * Create and display handle menu window.
+     * Updates app info and creates and displays handle menu window.
      */
-    void createHandleMenu(SplitScreenController splitScreenController) {
+    void createHandleMenu() {
+        // Requests assist content. When content is received, calls {@link #onAssistContentReceived}
+        // which sets app info and creates the handle menu.
+        mAssistContentRequester.requestAssistContent(
+                mTaskInfo.taskId, this::onAssistContentReceived);
+    }
+
+    /**
+     * Called when assist content is received. updates the saved links and creates the handle menu.
+     */
+    @VisibleForTesting
+    void onAssistContentReceived(@Nullable AssistContent assistContent) {
+        mWebUri = assistContent == null ? null : assistContent.getWebUri();
         loadAppInfoIfNeeded();
         updateGenericLink();
+
+        // Create and display handle menu
         mHandleMenu = mHandleMenuFactory.create(
                 this,
                 mWindowManagerWrapper,
                 mRelayoutParams.mLayoutResId,
                 mAppIconBitmap,
                 mAppName,
-                splitScreenController,
+                mSplitScreenController,
                 DesktopModeStatus.canEnterDesktopMode(mContext),
                 Flags.enableDesktopWindowingMultiInstanceFeatures()
                         && mMultiInstanceHelper
@@ -1012,6 +1041,7 @@
         mHandleMenu.show(
                 /* onToDesktopClickListener= */ () -> {
                     mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
+                    mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
                     return Unit.INSTANCE;
                 },
                 /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
@@ -1333,6 +1363,7 @@
                 SyncTransactionQueue syncQueue,
                 RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
                 AppToWebGenericLinksParser genericLinksParser,
+                AssistContentRequester assistContentRequester,
                 MultiInstanceHelper multiInstanceHelper) {
             return new DesktopModeWindowDecoration(
                     context,
@@ -1348,6 +1379,7 @@
                     syncQueue,
                     rootTaskDisplayAreaOrganizer,
                     genericLinksParser,
+                    assistContentRequester,
                     multiInstanceHelper);
         }
     }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index ec1d4f7..7640cb1 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -195,6 +195,25 @@
                             .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
+        val CORNER_RESIZE_TO_MAXIMUM_SIZE =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("CORNER_RESIZE_TO_MAXIMUM_SIZE"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions =
+                AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(
+                            AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+                            AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
+                            AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
         val SNAP_RESIZE_LEFT_WITH_BUTTON =
             FlickerConfigEntry(
                 scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_BUTTON"),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt
new file mode 100644
index 0000000..0b98ba2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the greatest possible height and width in
+ * landscape mode.
+ *
+ * Assert that the maximum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMaximumWindowSizeLandscape : ResizeAppWithCornerResize(
+    rotation = Rotation.ROTATION_90
+) {
+    @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"])
+    @Test
+    override fun resizeAppWithCornerResizeToMaximumSize() =
+        super.resizeAppWithCornerResizeToMaximumSize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt
new file mode 100644
index 0000000..b1c04d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.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.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the greatest possible height and width in
+ * portrait mode.
+ *
+ * Assert that the maximum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMaximumWindowSizePortrait : ResizeAppWithCornerResize() {
+    @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"])
+    @Test
+    override fun resizeAppWithCornerResizeToMaximumSize() =
+        super.resizeAppWithCornerResizeToMaximumSize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt
new file mode 100644
index 0000000..a4dc52b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [CloseAllAppsWithAppHeaderExit]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class CloseAllAppsWithAppHeaderExitTest() : CloseAllAppsWithAppHeaderExit()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt
new file mode 100644
index 0000000..3d95f97
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.EnterDesktopWithDrag
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [EnterDesktopWithDrag]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class EnterDesktopWithDragTest : EnterDesktopWithDrag()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..140c5ec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.ExitDesktopWithDragToTopDragZone
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [ExitDesktopWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class ExitDesktopWithDragToTopDragZoneTest : ExitDesktopWithDragToTopDragZone()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt
new file mode 100644
index 0000000..3d3dcd0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MaximizeAppWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MaximizeAppWindowTest : MaximizeAppWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt
new file mode 100644
index 0000000..263e89f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.OpenAppsInDesktopMode
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [OpenAppsInDesktopMode]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class OpenAppsInDesktopModeTest : OpenAppsInDesktopMode()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt
new file mode 100644
index 0000000..13f4775
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindowAndPip
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [ResizeAppCornerMultiWindowAndPip]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class ResizeAppCornerMultiWindowAndPipTest : ResizeAppCornerMultiWindowAndPip()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt
new file mode 100644
index 0000000..bc9bb41
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindow
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [ResizeAppCornerMultiWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class ResizeAppCornerMultiWindowTest : ResizeAppCornerMultiWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt
new file mode 100644
index 0000000..46168eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [ResizeAppWithCornerResize]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt
new file mode 100644
index 0000000..ee24200
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.server.wm.flicker.helpers.MotionEventHelper
+import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [ResizeAppWithEdgeResize]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class ResizeAppWithEdgeResizeTest :
+  ResizeAppWithEdgeResize(MotionEventHelper.InputMethod.TOUCHPAD)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt
new file mode 100644
index 0000000..38e85c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithButton
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [SnapResizeAppWindowWithButton]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class SnapResizeAppWindowWithButtonTest : SnapResizeAppWindowWithButton()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt
new file mode 100644
index 0000000..082a3fb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [SnapResizeAppWindowWithDrag]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class SnapResizeAppWindowWithDragTest : SnapResizeAppWindowWithDrag()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt
new file mode 100644
index 0000000..fdd0d81
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.SwitchToOverviewFromDesktop
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [SwitchToOverviewFromDesktop]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
index e9056f3..351a700 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -33,15 +32,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class CloseAllAppsWithAppHeaderExit
-@JvmOverloads
+@Ignore("Base Test Class")
+abstract class CloseAllAppsWithAppHeaderExit
 constructor(val rotation: Rotation = Rotation.ROTATION_0) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
index ca1dc1a..3f9927f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.MailAppHelper
@@ -26,13 +25,11 @@
 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.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
+@Ignore("Test Base Class")
+abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
 {
     private val imeAppHelper = ImeAppHelper(instrumentation)
     private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index f4d6414..967bd29 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
@@ -25,19 +24,16 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class EnterDesktopWithDrag
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class EnterDesktopWithDrag
 constructor(
     val rotation: Rotation = Rotation.ROTATION_0,
     isResizeable: Boolean = true,
-    isLandscapeApp: Boolean = true
+    isLandscapeApp: Boolean = true,
 ) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
 
     @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index b616e53..824c448 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
 import com.android.window.flags.Flags
@@ -24,19 +23,16 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ExitDesktopWithDragToTopDragZone
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class ExitDesktopWithDragToTopDragZone
 constructor(
     val rotation: Rotation = Rotation.ROTATION_0,
     isResizeable: Boolean = true,
-    isLandscapeApp: Boolean = true
+    isLandscapeApp: Boolean = true,
 ) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
 
     @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
index a5c794b..aad266f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.NavBar
 import android.tools.Rotation
@@ -36,14 +35,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) {
+@Ignore("Test Base Class")
+abstract class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
@@ -83,4 +80,4 @@
         secondApp.exit(wmHelper)
         firstApp.exit(wmHelper)
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
index b6bca7a..bfee318 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -34,15 +33,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ResizeAppCornerMultiWindow
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class ResizeAppCornerMultiWindow
 constructor(val rotation: Rotation = Rotation.ROTATION_0,
     val horizontalChange: Int = 50,
     val verticalChange: Int = -50) {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
index 285ea13..5b1b64e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -35,15 +34,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ResizeAppCornerMultiWindowAndPip
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class ResizeAppCornerMultiWindowAndPip
 constructor(val rotation: Rotation = Rotation.ROTATION_0,
     val horizontalChange: Int = 50,
     val verticalChange: Int = -50) {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index 42940a9..bd25639 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -32,16 +31,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ResizeAppWithCornerResize
-@JvmOverloads
-constructor(
+@Ignore("Test Base Class")
+abstract class ResizeAppWithCornerResize(
     val rotation: Rotation = Rotation.ROTATION_0,
     val horizontalChange: Int = 200,
     val verticalChange: Int = -200,
@@ -83,6 +78,25 @@
         )
     }
 
+    @Test
+    open fun resizeAppWithCornerResizeToMaximumSize() {
+        val maxResizeChange = 3000
+        testApp.cornerResize(
+            wmHelper,
+            device,
+            DesktopModeAppHelper.Corners.RIGHT_TOP,
+            maxResizeChange,
+            -maxResizeChange
+        )
+        testApp.cornerResize(
+            wmHelper,
+            device,
+            DesktopModeAppHelper.Corners.LEFT_BOTTOM,
+            -maxResizeChange,
+            maxResizeChange
+        )
+    }
+
     @After
     fun teardown() {
         testApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
index d094967..6780238 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -32,15 +31,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class ResizeAppWithEdgeResize
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class ResizeAppWithEdgeResize
 constructor(
     val inputMethod: MotionEventHelper.InputMethod,
     val rotation: Rotation = Rotation.ROTATION_90
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
index 33242db..2b40497 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -32,15 +31,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class SnapResizeAppWindowWithButton
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class SnapResizeAppWindowWithButton
 constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
index 14eb779..b4bd7e1 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.scenarios
 
 import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -32,15 +31,12 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class SnapResizeAppWindowWithDrag
-@JvmOverloads
+@Ignore("Test Base Class")
+abstract class SnapResizeAppWindowWithDrag
 constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -72,4 +68,4 @@
     fun teardown() {
         testApp.exit(wmHelper)
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
index 53e36e23..dad2eb6 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
@@ -31,20 +30,17 @@
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
 
 /**
 * Base test for opening recent apps overview from desktop mode.
 *
 * Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation.
 */
-@RunWith(BlockJUnit4ClassRunner::class)
-@Postsubmit
-open class SwitchToOverviewFromDesktop
-@JvmOverloads
+@Ignore("Base Test Class")
+abstract class SwitchToOverviewFromDesktop
 constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
deleted file mode 100644
index b1befc4..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.splitscreen;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.ActivityManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerTransaction;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/** Tests for {@link MainStage} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MainStageTests extends ShellTestCase {
-    @Mock private ShellTaskOrganizer mTaskOrganizer;
-    @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
-    @Mock private SyncTransactionQueue mSyncQueue;
-    @Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
-    @Mock private SurfaceControl mRootLeash;
-    @Mock private IconProvider mIconProvider;
-    private WindowContainerTransaction mWct = new WindowContainerTransaction();
-    private SurfaceSession mSurfaceSession = new SurfaceSession();
-    private MainStage mMainStage;
-
-    @Before
-    @UiThreadTest
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
-        mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
-        mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
-    }
-
-    @Test
-    public void testActiveDeactivate() {
-        mMainStage.activate(mWct, true /* reparent */);
-        assertThat(mMainStage.isActive()).isTrue();
-
-        mMainStage.deactivate(mWct);
-        assertThat(mMainStage.isActive()).isFalse();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 4de2278..751275b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -74,10 +74,10 @@
         final SurfaceControl mRootLeash;
 
         TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-                ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage,
-                DisplayController displayController, DisplayImeController imeController,
-                DisplayInsetsController insetsController, SplitLayout splitLayout,
-                Transitions transitions, TransactionPool transactionPool,
+                ShellTaskOrganizer taskOrganizer, StageTaskListener mainStage,
+                StageTaskListener sideStage, DisplayController displayController,
+                DisplayImeController imeController, DisplayInsetsController insetsController,
+                SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool,
                 ShellExecutor mainExecutor, Handler mainHandler,
                 Optional<RecentTasksController> recentTasks,
                 LaunchAdjacentController launchAdjacentController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e167433..af288c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -116,7 +116,7 @@
     @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
     private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
-    private MainStage mMainStage;
+    private StageTaskListener mMainStage;
     private StageTaskListener mSideStage;
     private StageCoordinator mStageCoordinator;
     private SplitScreenTransitions mSplitScreenTransitions;
@@ -133,7 +133,7 @@
         doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
         doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
-        mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+        mMainStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
                 mIconProvider, Optional.of(mWindowDecorViewModel)));
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index c9e1414..ce343b8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -97,7 +97,7 @@
     @Mock
     private SyncTransactionQueue mSyncQueue;
     @Mock
-    private MainStage mMainStage;
+    private StageTaskListener mMainStage;
     @Mock
     private StageTaskListener mSideStage;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index acd612e..8b5cb97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -199,4 +199,13 @@
         assertThat(mStageTaskListener.removeTask(task.taskId, null, mWct)).isTrue();
         verify(mWct).reparent(eq(task.token), isNull(), eq(false));
     }
+
+    @Test
+    public void testActiveDeactivate() {
+        mStageTaskListener.activate(mWct, true /* reparent */);
+        assertThat(mStageTaskListener.isActive()).isTrue();
+
+        mStageTaskListener.deactivate(mWct);
+        assertThat(mStageTaskListener.isActive()).isFalse();
+    }
 }
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 be0549b..3dd8a2b 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
@@ -73,6 +73,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser
+import com.android.wm.shell.apptoweb.AssistContentRequester
 import com.android.wm.shell.common.DisplayChangeController
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayInsetsController
@@ -165,6 +166,7 @@
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
     @Mock private lateinit var mockUserHandle: UserHandle
+    @Mock private lateinit var mockAssistContentRequester: AssistContentRequester
     @Mock private lateinit var mockToast: Toast
     private val bgExecutor = TestShellExecutor()
     @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
@@ -218,6 +220,7 @@
                 mockTransitions,
                 Optional.of(mockDesktopTasksController),
                 mockGenericLinksParser,
+                mockAssistContentRequester,
                 mockMultiInstanceHelper,
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
@@ -1131,7 +1134,7 @@
         whenever(
             mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
-                any(), any(), any())
+                any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
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 258c860..b9e542a0 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
@@ -46,6 +46,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.assist.AssistContent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -88,6 +89,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+import com.android.wm.shell.apptoweb.AssistContentRequester;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
@@ -133,6 +135,7 @@
 
     private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/");
     private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/");
+    private static final Uri TEST_URI3 = Uri.parse("https://slides.google.com/");
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
@@ -175,6 +178,8 @@
     @Mock
     private WindowManager mMockWindowManager;
     @Mock
+    private AssistContentRequester mMockAssistContentRequester;
+    @Mock
     private HandleMenu mMockHandleMenu;
     @Mock
     private HandleMenuFactory mMockHandleMenuFactory;
@@ -189,7 +194,8 @@
     private SurfaceControl.Transaction mMockTransaction;
     private StaticMockitoSession mMockitoSession;
     private TestableContext mTestableContext;
-    private ShellExecutor mBgExecutor = new TestShellExecutor();
+    private final ShellExecutor mBgExecutor = new TestShellExecutor();
+    private final AssistContent mAssistContent = new AssistContent();
 
     /** Set up run before test class. */
     @BeforeClass
@@ -673,10 +679,11 @@
     public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+                TEST_URI3 /* generic link */);
 
         // Verify handle menu's browser link set as captured link
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verifyHandleMenuCreated(TEST_URI1);
     }
 
@@ -685,7 +692,8 @@
     public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* generic link */);
         final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
 
         // Run runnable to set captured link to expired
@@ -694,7 +702,7 @@
 
         // Verify captured link is no longer valid by verifying link is not set as handle menu
         // browser link.
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verifyHandleMenuCreated(null /* uri */);
     }
 
@@ -703,7 +711,8 @@
     public void capturedLink_capturedLinkNotResetToSameLink() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* generic link */);
         final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
 
         // Run runnable to set captured link to expired
@@ -714,7 +723,7 @@
         decor.relayout(taskInfo);
 
         // Verify handle menu's browser link not set to captured link since link is expired
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verifyHandleMenuCreated(null /* uri */);
     }
 
@@ -723,11 +732,12 @@
     public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* generic link */);
         final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
 
         // Create handle menu before link expires
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
 
         // Run runnable to set captured link to expired
         verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
@@ -735,7 +745,7 @@
 
         // Verify handle menu's browser link is set to captured link since menu was opened before
         // captured link expired
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verifyHandleMenuCreated(TEST_URI1);
     }
 
@@ -744,12 +754,13 @@
     public void capturedLink_capturedLinkExpiresAfterClick() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* generic link */);
         final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
                 ArgumentCaptor.forClass(Function1.class);
 
         // Simulate menu opening and clicking open in browser button
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verify(mMockHandleMenu).show(
                 any(),
                 any(),
@@ -763,7 +774,7 @@
 
         // Verify handle menu's browser link not set to captured link since link not valid after
         // open in browser clicked
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verifyHandleMenuCreated(null /* uri */);
     }
 
@@ -772,10 +783,11 @@
     public void capturedLink_openInBrowserListenerCalledOnClick() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* generic link */);
         final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
                 ArgumentCaptor.forClass(Function1.class);
-        decor.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decor);
         verify(mMockHandleMenu).show(
                 any(),
                 any(),
@@ -793,24 +805,38 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() {
+    public void webUriLink_webUriLinkUsedWhenCapturedLinkUnavailable() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, null /* captured link */, TEST_URI2 /* generic link */);
-
-        // Verify handle menu's browser link set as generic link no captured link is available
-        decor.createHandleMenu(mMockSplitScreenController);
+                taskInfo, null /* captured link */, TEST_URI2 /* web uri */,
+                TEST_URI3 /* generic link */);
+        // Verify handle menu's browser link set as web uri link when captured link is unavailable
+        createHandleMenu(decor);
         verifyHandleMenuCreated(TEST_URI2);
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void genericLink_genericLinkUsedWhenCapturedLinkAndWebUriUnavailable() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, null /* captured link */, null /* web uri */,
+                TEST_URI3 /* generic link */);
+
+        // Verify handle menu's browser link set as generic link when captured link and web uri link
+        // are unavailable
+        createHandleMenu(decor);
+        verifyHandleMenuCreated(TEST_URI3);
+    }
+
+    @Test
     public void handleMenu_onCloseMenuClick_closesMenu() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
                 true /* relayout */);
         final ArgumentCaptor<Function0<Unit>> closeClickListener =
                 ArgumentCaptor.forClass(Function0.class);
-        decoration.createHandleMenu(mMockSplitScreenController);
+        createHandleMenu(decoration);
         verify(mMockHandleMenu).show(
                 any(),
                 any(),
@@ -860,9 +886,10 @@
 
     private DesktopModeWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink,
-            @Nullable Uri genericLink) {
+            @Nullable Uri webUri, @Nullable Uri genericLink) {
         taskInfo.capturedLink = capturedLink;
         taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
+        mAssistContent.setWebUri(webUri);
         final String genericLinkString = genericLink == null ? null : genericLink.toString();
         doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
         // Relayout to set captured link
@@ -894,11 +921,10 @@
                 mContext, mMockDisplayController, mMockSplitScreenController,
                 mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor,
                 mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
-                mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier,
-                WindowContainerTransaction::new, SurfaceControl::new,
-                new WindowManagerWrapper(mMockWindowManager),
-                mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory,
-                mMockMultiInstanceHelper);
+                mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
+                mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
+                new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
+                maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
         windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
@@ -926,6 +952,13 @@
 
     }
 
+    private void createHandleMenu(@NonNull DesktopModeWindowDecoration decor) {
+        decor.createHandleMenu();
+        // Call DesktopModeWindowDecoration#onAssistContentReceived because decor waits to receive
+        // {@link AssistContent} before creating the menu
+        decor.onAssistContentReceived(mAssistContent);
+    }
+
     private static boolean hasNoInputChannelFeature(RelayoutParams params) {
         return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL)
                 != 0;
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index 09232b6..a58493a 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -7,6 +7,17 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+cc_library_headers {
+    name: "libhostgraphics_headers",
+    host_supported: true,
+    export_include_dirs: ["include"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
+}
+
 cc_library_host_static {
     name: "libhostgraphics",
 
@@ -30,12 +41,13 @@
     ],
 
     header_libs: [
+        "libhostgraphics_headers",
         "libnativebase_headers",
         "libnativedisplay_headers",
         "libnativewindow_headers",
     ],
 
-    export_include_dirs: ["."],
+    export_include_dirs: ["include"],
 
     target: {
         windows: {
diff --git a/libs/hostgraphics/gui/BufferItem.h b/libs/hostgraphics/include/gui/BufferItem.h
similarity index 100%
rename from libs/hostgraphics/gui/BufferItem.h
rename to libs/hostgraphics/include/gui/BufferItem.h
diff --git a/libs/hostgraphics/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h
similarity index 100%
rename from libs/hostgraphics/gui/BufferItemConsumer.h
rename to libs/hostgraphics/include/gui/BufferItemConsumer.h
diff --git a/libs/hostgraphics/gui/BufferQueue.h b/libs/hostgraphics/include/gui/BufferQueue.h
similarity index 100%
rename from libs/hostgraphics/gui/BufferQueue.h
rename to libs/hostgraphics/include/gui/BufferQueue.h
diff --git a/libs/hostgraphics/gui/ConsumerBase.h b/libs/hostgraphics/include/gui/ConsumerBase.h
similarity index 100%
rename from libs/hostgraphics/gui/ConsumerBase.h
rename to libs/hostgraphics/include/gui/ConsumerBase.h
diff --git a/libs/hostgraphics/gui/IGraphicBufferConsumer.h b/libs/hostgraphics/include/gui/IGraphicBufferConsumer.h
similarity index 100%
rename from libs/hostgraphics/gui/IGraphicBufferConsumer.h
rename to libs/hostgraphics/include/gui/IGraphicBufferConsumer.h
diff --git a/libs/hostgraphics/gui/IGraphicBufferProducer.h b/libs/hostgraphics/include/gui/IGraphicBufferProducer.h
similarity index 100%
rename from libs/hostgraphics/gui/IGraphicBufferProducer.h
rename to libs/hostgraphics/include/gui/IGraphicBufferProducer.h
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/include/gui/Surface.h
similarity index 100%
rename from libs/hostgraphics/gui/Surface.h
rename to libs/hostgraphics/include/gui/Surface.h
diff --git a/libs/hostgraphics/ui/Fence.h b/libs/hostgraphics/include/ui/Fence.h
similarity index 100%
rename from libs/hostgraphics/ui/Fence.h
rename to libs/hostgraphics/include/ui/Fence.h
diff --git a/libs/hostgraphics/ui/GraphicBuffer.h b/libs/hostgraphics/include/ui/GraphicBuffer.h
similarity index 100%
rename from libs/hostgraphics/ui/GraphicBuffer.h
rename to libs/hostgraphics/include/ui/GraphicBuffer.h
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index c1c30f5..c0cedf1 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -25,14 +25,6 @@
 
 namespace text_feature {
 
-inline bool fix_double_underline() {
-#ifdef __ANDROID__
-    return com_android_text_flags_fix_double_underline();
-#else
-    return true;
-#endif  // __ANDROID__
-}
-
 inline bool deprecate_ui_fonts() {
 #ifdef __ANDROID__
     return com_android_text_flags_deprecate_ui_fonts();
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index a2748b0..236c3736 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -318,6 +318,11 @@
     return has101012Support;
 }
 
+bool HardwareBitmapUploader::has10101010Support() {
+    static bool has1010110Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM);
+    return has1010110Support;
+}
+
 bool HardwareBitmapUploader::hasAlpha8Support() {
     static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM);
     return hasAlpha8Support;
@@ -376,6 +381,19 @@
             }
             formatInfo.format = GL_RGBA;
             break;
+        case kRGBA_10x6_SkColorType:
+            formatInfo.isSupported = HardwareBitmapUploader::has10101010Support();
+            if (formatInfo.isSupported) {
+                formatInfo.type = 0;  // Not supported in GL
+                formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
+                formatInfo.vkFormat = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16;
+            } else {
+                formatInfo.type = GL_UNSIGNED_BYTE;
+                formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+                formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+            }
+            formatInfo.format = 0;  // Not supported in GL
+            break;
         case kAlpha_8_SkColorType:
             formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
             if (formatInfo.isSupported) {
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 00ee996..76cb80b 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -33,12 +33,14 @@
 #ifdef __ANDROID__
     static bool hasFP16Support();
     static bool has1010102Support();
+    static bool has10101010Support();
     static bool hasAlpha8Support();
 #else
     static bool hasFP16Support() {
         return true;
     }
     static bool has1010102Support() { return true; }
+    static bool has10101010Support() { return true; }
     static bool hasAlpha8Support() { return true; }
 #endif
 };
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 72e83af..9e825fb 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -841,9 +841,6 @@
     sk_sp<SkTextBlob> textBlob(builder.make());
 
     applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
-    if (!text_feature::fix_double_underline()) {
-        drawTextDecorations(x, y, totalAdvance, paintCopy);
-    }
 }
 
 void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 80b6c03..5af4af2 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -110,28 +110,26 @@
     DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
 
-    if (text_feature::fix_double_underline()) {
-        Paint copied(paint);
-        PaintFilter* filter = getPaintFilter();
-        if (filter != nullptr) {
-            filter->filterFullPaint(&copied);
+    Paint copied(paint);
+    PaintFilter* filter = getPaintFilter();
+    if (filter != nullptr) {
+        filter->filterFullPaint(&copied);
+    }
+    const bool isUnderline = copied.isUnderline();
+    const bool isStrikeThru = copied.isStrikeThru();
+    if (isUnderline || isStrikeThru) {
+        const SkScalar left = x;
+        const SkScalar right = x + layout.getAdvance();
+        if (isUnderline) {
+            const SkScalar top = y + f.getUnderlinePosition();
+            drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
         }
-        const bool isUnderline = copied.isUnderline();
-        const bool isStrikeThru = copied.isStrikeThru();
-        if (isUnderline || isStrikeThru) {
-            const SkScalar left = x;
-            const SkScalar right = x + layout.getAdvance();
-            if (isUnderline) {
-                const SkScalar top = y + f.getUnderlinePosition();
-                drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
-            }
-            if (isStrikeThru) {
-                float textSize = paint.getSkFont().getSize();
-                const float position = textSize * Paint::kStdStrikeThru_Top;
-                const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
-                const SkScalar top = y + position;
-                drawStroke(left, right, top, thickness, copied, this);
-            }
+        if (isStrikeThru) {
+            float textSize = paint.getSkFont().getSize();
+            const float position = textSize * Paint::kStdStrikeThru_Top;
+            const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+            const SkScalar top = y + position;
+            drawStroke(left, right, top, thickness, copied, this);
         }
     }
 }
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 0efb2c8..d7bf201 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -142,32 +142,30 @@
             canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
         }
 
-        if (text_feature::fix_double_underline()) {
-            // Extract underline position and thickness.
-            if (paint.isUnderline()) {
-                SkFontMetrics metrics;
-                paint.getSkFont().getMetrics(&metrics);
-                const float textSize = paint.getSkFont().getSize();
-                SkScalar position;
-                if (!metrics.hasUnderlinePosition(&position)) {
-                    position = textSize * Paint::kStdUnderline_Top;
-                }
-                SkScalar thickness;
-                if (!metrics.hasUnderlineThickness(&thickness)) {
-                    thickness = textSize * Paint::kStdUnderline_Thickness;
-                }
-
-                // If multiple fonts are used, use the most bottom position and most thick stroke
-                // width as the underline position. This follows the CSS standard:
-                // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
-                // <quote>
-                // The exact position and thickness of line decorations is UA-defined in this level.
-                // However, for underlines and overlines the UA must use a single thickness and
-                // position on each line for the decorations deriving from a single decorating box.
-                // </quote>
-                underlinePosition = std::max(underlinePosition, position);
-                underlineThickness = std::max(underlineThickness, thickness);
+        // Extract underline position and thickness.
+        if (paint.isUnderline()) {
+            SkFontMetrics metrics;
+            paint.getSkFont().getMetrics(&metrics);
+            const float textSize = paint.getSkFont().getSize();
+            SkScalar position;
+            if (!metrics.hasUnderlinePosition(&position)) {
+                position = textSize * Paint::kStdUnderline_Top;
             }
+            SkScalar thickness;
+            if (!metrics.hasUnderlineThickness(&thickness)) {
+                thickness = textSize * Paint::kStdUnderline_Thickness;
+            }
+
+            // If multiple fonts are used, use the most bottom position and most thick stroke
+            // width as the underline position. This follows the CSS standard:
+            // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+            // <quote>
+            // The exact position and thickness of line decorations is UA-defined in this level.
+            // However, for underlines and overlines the UA must use a single thickness and
+            // position on each line for the decorations deriving from a single decorating box.
+            // </quote>
+            underlinePosition = std::max(underlinePosition, position);
+            underlineThickness = std::max(underlineThickness, thickness);
         }
     }
 
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index bce84ae..e302393 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -318,6 +318,15 @@
         tailPNext = &deviceFaultFeatures->pNext;
     }
 
+    if (grExtensions.hasExtension(VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME, 1)) {
+        VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT* formatFeatures =
+                new VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT;
+        formatFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT;
+        formatFeatures->pNext = nullptr;
+        *tailPNext = formatFeatures;
+        tailPNext = &formatFeatures->pNext;
+    }
+
     // query to get the physical device features
     mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
     // this looks like it would slow things down,
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
index c70a304..ecb06d8 100644
--- a/libs/hwui/tests/unit/UnderlineTest.cpp
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -109,9 +109,7 @@
     return f;
 }
 
-TEST_WITH_FLAGS(UnderlineTest, Roboto,
-                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
-                                                    fix_double_underline))) {
+TEST(UnderlineTest, Roboto) {
     float textSize = 100;
     Paint paint;
     paint.getSkFont().setSize(textSize);
@@ -123,9 +121,7 @@
     EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
 }
 
-TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
-                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
-                                                    fix_double_underline))) {
+TEST(UnderlineTest, NotoCJK) {
     float textSize = 100;
     Paint paint;
     paint.getSkFont().setSize(textSize);
@@ -137,9 +133,7 @@
     EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
 }
 
-TEST_WITH_FLAGS(UnderlineTest, Mixture,
-                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
-                                                    fix_double_underline))) {
+TEST(UnderlineTest, Mixture) {
     float textSize = 100;
     Paint paint;
     paint.getSkFont().setSize(textSize);
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 6a560b3..9673c5f 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -49,6 +49,10 @@
             colorType = kRGBA_1010102_SkColorType;
             alphaType = kPremul_SkAlphaType;
             break;
+        case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
+            colorType = kRGBA_10x6_SkColorType;
+            alphaType = kPremul_SkAlphaType;
+            break;
         case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
             colorType = kRGBA_F16_SkColorType;
             alphaType = kPremul_SkAlphaType;
@@ -86,6 +90,8 @@
             return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
         case kRGBA_1010102_SkColorType:
             return AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
+        case kRGBA_10x6_SkColorType:
+            return AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
         case kARGB_4444_SkColorType:
             // Hardcoding the value from android::PixelFormat
             static constexpr uint64_t kRGBA4444 = 7;
@@ -108,6 +114,8 @@
             return kRGB_565_SkColorType;
         case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
             return kRGBA_1010102_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
+            return kRGBA_10x6_SkColorType;
         case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
             return kRGBA_F16_SkColorType;
         case AHARDWAREBUFFER_FORMAT_R8_UNORM:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 25fae76..4981cb3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6984,6 +6984,27 @@
     }
 
     /**
+     * Test method for enabling/disabling the volume controller long press timeout for checking
+     * whether two consecutive volume adjustments should be treated as a volume long press.
+     *
+     * <p>Used only for testing
+     *
+     * @param enable true for enabling, otherwise will be disabled (test mode)
+     *
+     * @hide
+     **/
+    @TestApi
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) {
+        try {
+            getService().setVolumeControllerLongPressTimeoutEnabled(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Only useful for volume controllers.
      * @hide
      */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a96562d..9af6b28 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -303,6 +303,9 @@
 
     void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
 
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    oneway void setVolumeControllerLongPressTimeoutEnabled(boolean enable);
+
     boolean isStreamAffectedByRingerMode(int streamType);
 
     boolean isStreamAffectedByMute(int streamType);
diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp
index 86c8f0da..6840e10 100644
--- a/packages/SettingsLib/DataStore/Android.bp
+++ b/packages/SettingsLib/DataStore/Android.bp
@@ -17,6 +17,7 @@
         "androidx.annotation_annotation",
         "androidx.collection_collection-ktx",
         "androidx.core_core-ktx",
+        "error_prone_annotations",
         "guava",
     ],
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
new file mode 100644
index 0000000..3d41337
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.SharedPreferences
+
+/** Interface of key-value store. */
+interface KeyValueStore : KeyedObservable<String> {
+
+    /** Returns if the storage contains persistent value of given key. */
+    fun contains(key: String): Boolean
+
+    /** Gets default value of given key. */
+    fun <T : Any> getDefaultValue(key: String, valueType: Class<T>): T? =
+        when (valueType) {
+            Boolean::class.javaObjectType -> false
+            Float::class.javaObjectType -> 0f
+            Int::class.javaObjectType -> 0
+            Long::class.javaObjectType -> 0
+            else -> null
+        }
+            as T?
+
+    /** Gets value of given key. */
+    fun <T : Any> getValue(key: String, valueType: Class<T>): T?
+
+    /**
+     * Sets value for given key.
+     *
+     * @param key key
+     * @param valueType value type
+     * @param value value to set, null means remove
+     */
+    fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?)
+}
+
+/** [SharedPreferences] based [KeyValueStore]. */
+interface SharedPreferencesKeyValueStore : KeyValueStore {
+
+    /** [SharedPreferences] of the key-value store. */
+    val sharedPreferences: SharedPreferences
+
+    override fun contains(key: String): Boolean = sharedPreferences.contains(key)
+
+    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? =
+        when (valueType) {
+            Boolean::class.javaObjectType -> sharedPreferences.getBoolean(key, false)
+            Float::class.javaObjectType -> sharedPreferences.getFloat(key, 0f)
+            Int::class.javaObjectType -> sharedPreferences.getInt(key, 0)
+            Long::class.javaObjectType -> sharedPreferences.getLong(key, 0)
+            String::class.javaObjectType -> sharedPreferences.getString(key, null)
+            Set::class.javaObjectType -> sharedPreferences.getStringSet(key, null)
+            else -> null
+        }
+            as T?
+
+    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+        if (value == null) {
+            sharedPreferences.edit().remove(key).apply()
+            return
+        }
+        val edit = sharedPreferences.edit()
+        when (valueType) {
+            Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean)
+            Float::class.javaObjectType -> edit.putFloat(key, value as Float)
+            Int::class.javaObjectType -> edit.putInt(key, value as Int)
+            Long::class.javaObjectType -> edit.putLong(key, value as Long)
+            String::class.javaObjectType -> edit.putString(key, value as String)
+            Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>)
+            else -> {}
+        }
+        edit.apply()
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 4ce1d37..ec90317 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.AnyThread
 import androidx.annotation.GuardedBy
 import androidx.collection.MutableScatterMap
+import com.google.errorprone.annotations.CanIgnoreReturnValue
 import java.util.WeakHashMap
 import java.util.concurrent.Executor
 
@@ -62,8 +63,9 @@
      *
      * @param observer observer to be notified
      * @param executor executor to run the callback
+     * @return if the observer is newly added
      */
-    fun addObserver(observer: KeyedObserver<K?>, executor: Executor)
+    @CanIgnoreReturnValue fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean
 
     /**
      * Adds an observer on given key.
@@ -73,14 +75,24 @@
      * @param key key to observe
      * @param observer observer to be notified
      * @param executor executor to run the callback
+     * @return if the observer is newly added
      */
-    fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor)
+    @CanIgnoreReturnValue
+    fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean
 
-    /** Removes observer. */
-    fun removeObserver(observer: KeyedObserver<K?>)
+    /**
+     * Removes observer.
+     *
+     * @return if the observer is found and removed
+     */
+    @CanIgnoreReturnValue fun removeObserver(observer: KeyedObserver<K?>): Boolean
 
-    /** Removes observer on given key. */
-    fun removeObserver(key: K, observer: KeyedObserver<K>)
+    /**
+     * Removes observer on given key.
+     *
+     * @return if the observer is found and removed
+     */
+    @CanIgnoreReturnValue fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean
 
     /**
      * Notifies all observers that a change occurs.
@@ -111,14 +123,17 @@
     @GuardedBy("itself")
     private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>()
 
-    override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) {
+    @CanIgnoreReturnValue
+    override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean {
         val oldExecutor = synchronized(observers) { observers.put(observer, executor) }
         if (oldExecutor != null && oldExecutor != executor) {
             throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
         }
+        return oldExecutor == null
     }
 
-    override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) {
+    @CanIgnoreReturnValue
+    override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean {
         val oldExecutor =
             synchronized(keyedObservers) {
                 keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor)
@@ -126,20 +141,23 @@
         if (oldExecutor != null && oldExecutor != executor) {
             throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
         }
+        return oldExecutor == null
     }
 
-    override fun removeObserver(observer: KeyedObserver<K?>) {
-        synchronized(observers) { observers.remove(observer) }
-    }
+    @CanIgnoreReturnValue
+    override fun removeObserver(observer: KeyedObserver<K?>) =
+        synchronized(observers) { observers.remove(observer) } != null
 
-    override fun removeObserver(key: K, observer: KeyedObserver<K>) {
+    @CanIgnoreReturnValue
+    override fun removeObserver(key: K, observer: KeyedObserver<K>) =
         synchronized(keyedObservers) {
-            val observers = keyedObservers[key]
-            if (observers?.remove(observer) != null && observers.isEmpty()) {
+            val observers = keyedObservers[key] ?: return false
+            val removed = observers.remove(observer) != null
+            if (removed && observers.isEmpty()) {
                 keyedObservers.remove(key)
             }
+            removed
         }
-    }
 
     override fun notifyChange(reason: Int) {
         // make a copy to avoid potential ConcurrentModificationException
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
new file mode 100644
index 0000000..4aef0fc
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.ContentResolver
+import android.content.Context
+import android.provider.Settings.Global
+import android.provider.Settings.SettingNotFoundException
+
+/**
+ * [KeyValueStore] for [Global] settings.
+ *
+ * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`.
+ */
+class SettingsGlobalStore private constructor(contentResolver: ContentResolver) :
+    SettingsStore(contentResolver) {
+
+    override val tag: String
+        get() = "SettingsGlobalStore"
+
+    override fun contains(key: String): Boolean = Global.getString(contentResolver, key) != null
+
+    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? =
+        try {
+            when (valueType) {
+                Boolean::class.javaObjectType -> Global.getInt(contentResolver, key) != 0
+                Float::class.javaObjectType -> Global.getFloat(contentResolver, key)
+                Int::class.javaObjectType -> Global.getInt(contentResolver, key)
+                Long::class.javaObjectType -> Global.getLong(contentResolver, key)
+                String::class.javaObjectType -> Global.getString(contentResolver, key)
+                else -> throw UnsupportedOperationException("Get $key $valueType")
+            }
+                as T?
+        } catch (e: SettingNotFoundException) {
+            null
+        }
+
+    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+        if (value == null) {
+            Global.putString(contentResolver, key, null)
+            return
+        }
+        when (valueType) {
+            Boolean::class.javaObjectType ->
+                Global.putInt(contentResolver, key, if (value == true) 1 else 0)
+            Float::class.javaObjectType -> Global.putFloat(contentResolver, key, value as Float)
+            Int::class.javaObjectType -> Global.putInt(contentResolver, key, value as Int)
+            Long::class.javaObjectType -> Global.putLong(contentResolver, key, value as Long)
+            String::class.javaObjectType -> Global.putString(contentResolver, key, value as String)
+            else -> throw UnsupportedOperationException("Set $key $valueType")
+        }
+    }
+
+    companion object {
+        @Volatile private var instance: SettingsGlobalStore? = null
+
+        @JvmStatic
+        fun get(context: Context): SettingsGlobalStore =
+            instance
+                ?: synchronized(this) {
+                    instance
+                        ?: SettingsGlobalStore(context.applicationContext.contentResolver).also {
+                            instance = it
+                        }
+                }
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
new file mode 100644
index 0000000..9f41ecb
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.ContentResolver
+import android.content.Context
+import android.provider.Settings.Secure
+import android.provider.Settings.SettingNotFoundException
+
+/**
+ * [KeyValueStore] for [Secure] settings.
+ *
+ * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`.
+ */
+class SettingsSecureStore private constructor(contentResolver: ContentResolver) :
+    SettingsStore(contentResolver) {
+
+    override val tag: String
+        get() = "SettingsSecureStore"
+
+    override fun contains(key: String): Boolean = Secure.getString(contentResolver, key) != null
+
+    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? =
+        try {
+            when (valueType) {
+                Boolean::class.javaObjectType -> Secure.getInt(contentResolver, key) != 0
+                Float::class.javaObjectType -> Secure.getFloat(contentResolver, key)
+                Int::class.javaObjectType -> Secure.getInt(contentResolver, key)
+                Long::class.javaObjectType -> Secure.getLong(contentResolver, key)
+                String::class.javaObjectType -> Secure.getString(contentResolver, key)
+                else -> throw UnsupportedOperationException("Get $key $valueType")
+            }
+                as T?
+        } catch (e: SettingNotFoundException) {
+            null
+        }
+
+    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+        if (value == null) {
+            Secure.putString(contentResolver, key, null)
+            return
+        }
+        when (valueType) {
+            Boolean::class.javaObjectType ->
+                Secure.putInt(contentResolver, key, if (value == true) 1 else 0)
+            Float::class.javaObjectType -> Secure.putFloat(contentResolver, key, value as Float)
+            Int::class.javaObjectType -> Secure.putInt(contentResolver, key, value as Int)
+            Long::class.javaObjectType -> Secure.putLong(contentResolver, key, value as Long)
+            String::class.javaObjectType -> Secure.putString(contentResolver, key, value as String)
+            else -> throw UnsupportedOperationException("Set $key $valueType")
+        }
+    }
+
+    companion object {
+        @Volatile private var instance: SettingsSecureStore? = null
+
+        @JvmStatic
+        fun get(context: Context): SettingsSecureStore =
+            instance
+                ?: synchronized(this) {
+                    instance
+                        ?: SettingsSecureStore(context.applicationContext.contentResolver).also {
+                            instance = it
+                        }
+                }
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
new file mode 100644
index 0000000..5981688
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.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.settingslib.datastore
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.util.Log
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+
+/** Base class of the Settings provider data stores. */
+open abstract class SettingsStore(protected val contentResolver: ContentResolver) :
+    KeyedDataObservable<String>(), KeyValueStore {
+
+    /**
+     * Counter of observers.
+     *
+     * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When
+     * an observer is not removed (and its weak reference is garbage collected), the content
+     * observer is not unregistered but this is not a big deal.
+     */
+    private val counter = AtomicInteger()
+
+    private val contentObserver =
+        object : ContentObserver(Handler(Looper.getMainLooper())) {
+            override fun onChange(selfChange: Boolean) {
+                super.onChange(selfChange, null)
+            }
+
+            override fun onChange(selfChange: Boolean, uri: Uri?) {
+                val key = uri?.lastPathSegment ?: return
+                notifyChange(key, DataChangeReason.UPDATE)
+            }
+        }
+
+    override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
+        if (super.addObserver(observer, executor)) {
+            onObserverAdded()
+            true
+        } else {
+            false
+        }
+
+    override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
+        if (super.addObserver(key, observer, executor)) {
+            onObserverAdded()
+            true
+        } else {
+            false
+        }
+
+    private fun onObserverAdded() {
+        if (counter.getAndIncrement() != 0) return
+        Log.i(tag, "registerContentObserver")
+        contentResolver.registerContentObserver(
+            Settings.Global.getUriFor(""),
+            true,
+            contentObserver,
+        )
+    }
+
+    override fun removeObserver(observer: KeyedObserver<String?>) =
+        if (super.removeObserver(observer)) {
+            onObserverRemoved()
+            true
+        } else {
+            false
+        }
+
+    override fun removeObserver(key: String, observer: KeyedObserver<String>) =
+        if (super.removeObserver(key, observer)) {
+            onObserverRemoved()
+            true
+        } else {
+            false
+        }
+
+    private fun onObserverRemoved() {
+        if (counter.decrementAndGet() != 0) return
+        Log.i(tag, "unregisterContentObserver")
+        contentResolver.unregisterContentObserver(contentObserver)
+    }
+
+    /** Tag for logging. */
+    abstract val tag: String
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
new file mode 100644
index 0000000..6cca7ed
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.ContentResolver
+import android.content.Context
+import android.provider.Settings.SettingNotFoundException
+import android.provider.Settings.System
+
+/**
+ * [KeyValueStore] for [System] settings.
+ *
+ * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`.
+ */
+class SettingsSystemStore private constructor(contentResolver: ContentResolver) :
+    SettingsStore(contentResolver) {
+
+    override val tag: String
+        get() = "SettingsSystemStore"
+
+    override fun contains(key: String): Boolean = System.getString(contentResolver, key) != null
+
+    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? =
+        try {
+            when (valueType) {
+                Boolean::class.javaObjectType -> System.getInt(contentResolver, key) != 0
+                Float::class.javaObjectType -> System.getFloat(contentResolver, key)
+                Int::class.javaObjectType -> System.getInt(contentResolver, key)
+                Long::class.javaObjectType -> System.getLong(contentResolver, key)
+                String::class.javaObjectType -> System.getString(contentResolver, key)
+                else -> throw UnsupportedOperationException("Get $key $valueType")
+            }
+                as T?
+        } catch (e: SettingNotFoundException) {
+            null
+        }
+
+    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+        if (value == null) {
+            System.putString(contentResolver, key, null)
+            return
+        }
+        when (valueType) {
+            Boolean::class.javaObjectType ->
+                System.putInt(contentResolver, key, if (value == true) 1 else 0)
+            Float::class.javaObjectType -> System.putFloat(contentResolver, key, value as Float)
+            Int::class.javaObjectType -> System.putInt(contentResolver, key, value as Int)
+            Long::class.javaObjectType -> System.putLong(contentResolver, key, value as Long)
+            String::class.javaObjectType -> System.putString(contentResolver, key, value as String)
+            else -> throw UnsupportedOperationException("Set $key $valueType")
+        }
+    }
+
+    companion object {
+        @Volatile private var instance: SettingsSystemStore? = null
+
+        @JvmStatic
+        fun get(context: Context): SettingsSystemStore =
+            instance
+                ?: synchronized(this) {
+                    instance
+                        ?: SettingsSystemStore(context.applicationContext.contentResolver).also {
+                            instance = it
+                        }
+                }
+    }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
index 0ca91cd..ea17a56 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
@@ -40,8 +40,9 @@
  * 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 name Name of the backup restore storage
+ * @param sharedPreferences SharedPreferences object
+ * @param filePath shared preferences file path relative to data dir
  * @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.
  */
@@ -50,12 +51,14 @@
 constructor(
     context: Context,
     override val name: String,
-    @get:VisibleForTesting internal val sharedPreferences: SharedPreferences,
+    override val sharedPreferences: SharedPreferences,
+    filePath: String = getSharedPreferencesFilePath(context, name),
     private val codec: BackupCodec? = null,
     private val verbose: Boolean = defaultVerbose(),
     private val filter: (String, Any?) -> Boolean = { _, _ -> true },
 ) :
-    BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)),
+    BackupRestoreFileStorage(context, filePath),
+    SharedPreferencesKeyValueStore,
     KeyedObservable<String> by KeyedDataObservable() {
 
     @JvmOverloads
@@ -66,7 +69,15 @@
         codec: BackupCodec? = null,
         verbose: Boolean = defaultVerbose(),
         filter: (String, Any?) -> Boolean = { _, _ -> true },
-    ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter)
+    ) : this(
+        context,
+        name,
+        context.getSharedPreferences(name, mode),
+        getSharedPreferencesFilePath(context, name),
+        codec,
+        verbose,
+        filter,
+    )
 
     /** Name of the intermediate SharedPreferences. */
     @VisibleForTesting
@@ -80,7 +91,15 @@
             return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS)
         }
 
-    private val sharedPreferencesListener = createSharedPreferenceListener()
+    private val sharedPreferencesListener =
+        SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+            if (key != null) {
+                notifyChange(key, DataChangeReason.UPDATE)
+            } else {
+                // On Android >= R, SharedPreferences.Editor.clear() will trigger this case
+                notifyChange(DataChangeReason.DELETE)
+            }
+        }
 
     init {
         // listener is weakly referenced, so unregister is optional
@@ -183,7 +202,8 @@
                 else -> {
                     Log.e(
                         LOG_TAG,
-                        "[$name] $operation $key=$value, unknown type: ${value?.javaClass}")
+                        "[$name] $operation $key=$value, unknown type: ${value?.javaClass}",
+                    )
                 }
             }
         }
@@ -191,14 +211,31 @@
     }
 
     companion object {
-        private fun Context.getSharedPreferencesFilePath(name: String): String {
-            val file = getSharedPreferencesFile(name)
-            return file.relativeTo(dataDirCompat).toString()
+        /** Returns the storage object of default [SharedPreferences]. */
+        @JvmStatic
+        fun getDefault(context: Context, name: String): SharedPreferencesStorage {
+            val prefName = getDefaultSharedPreferencesName(context)
+            return SharedPreferencesStorage(
+                context,
+                name,
+                context.getSharedPreferences(prefName, Context.MODE_PRIVATE),
+                getSharedPreferencesFilePath(context, prefName),
+            )
+        }
+
+        /** Returns the name of default [SharedPreferences]. */
+        @JvmStatic
+        fun getDefaultSharedPreferencesName(context: Context) = context.packageName + "_preferences"
+
+        /** Returns the shared preferences file path relative to data dir. */
+        @JvmStatic
+        fun getSharedPreferencesFilePath(context: Context, name: String): String {
+            val file = context.getSharedPreferencesFile(name)
+            return file.relativeTo(context.dataDirCompat).toString()
         }
 
         /** Returns the absolute path of shared preferences file. */
-        @JvmStatic
-        fun Context.getSharedPreferencesFile(name: String): File {
+        private fun Context.getSharedPreferencesFile(name: String): File {
             // ContextImpl.getSharedPreferencesPath is private
             return File(getSharedPreferencesDir(), "$name.xml")
         }
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
index 0fdecb0..c99d4b3 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
@@ -45,14 +45,14 @@
 
     @Test
     fun addObserver_sameExecutor() {
-        keyedObservable.addObserver(observer1, executor1)
-        keyedObservable.addObserver(observer1, executor1)
+        assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(observer1, executor1)).isFalse()
     }
 
     @Test
     fun addObserver_keyedObserver_sameExecutor() {
-        keyedObservable.addObserver(key1, keyedObserver1, executor1)
-        keyedObservable.addObserver(key1, keyedObserver1, executor1)
+        assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isFalse()
     }
 
     @Test
@@ -109,15 +109,15 @@
 
     @Test
     fun addObserver_notifyObservers_removeObserver() {
-        keyedObservable.addObserver(observer1, executor1)
-        keyedObservable.addObserver(observer2, executor2)
+        assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(observer2, executor2)).isTrue()
 
         keyedObservable.notifyChange(DataChangeReason.UPDATE)
         verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE)
         verify(observer2).onKeyChanged(null, DataChangeReason.UPDATE)
 
         reset(observer1, observer2)
-        keyedObservable.removeObserver(observer2)
+        assertThat(keyedObservable.removeObserver(observer2)).isTrue()
 
         keyedObservable.notifyChange(DataChangeReason.DELETE)
         verify(observer1).onKeyChanged(null, DataChangeReason.DELETE)
@@ -126,15 +126,15 @@
 
     @Test
     fun addObserver_keyedObserver_notifyObservers_removeObserver() {
-        keyedObservable.addObserver(key1, keyedObserver1, executor1)
-        keyedObservable.addObserver(key2, keyedObserver2, executor2)
+        assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor2)).isTrue()
 
         keyedObservable.notifyChange(key1, DataChangeReason.UPDATE)
         verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE)
         verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.UPDATE)
 
         reset(keyedObserver1, keyedObserver2)
-        keyedObservable.removeObserver(key1, keyedObserver1)
+        assertThat(keyedObservable.removeObserver(key1, keyedObserver1)).isTrue()
 
         keyedObservable.notifyChange(key1, DataChangeReason.DELETE)
         verify(keyedObserver1, never()).onKeyChanged(key1, DataChangeReason.DELETE)
@@ -143,9 +143,9 @@
 
     @Test
     fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() {
-        keyedObservable.addObserver(observer1, executor1)
-        keyedObservable.addObserver(key1, keyedObserver1, executor1)
-        keyedObservable.addObserver(key2, keyedObserver2, executor1)
+        assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue()
+        assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor1)).isTrue()
 
         keyedObservable.notifyChange(DataChangeReason.UPDATE)
         verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE)
@@ -171,25 +171,25 @@
     fun notifyChange_addObserverWithinCallback() {
         // ConcurrentModificationException is raised if it is not implemented correctly
         val observer: KeyedObserver<Any?> = KeyedObserver { _, _ ->
-            keyedObservable.addObserver(observer1, executor1)
+            assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue()
         }
 
-        keyedObservable.addObserver(observer, executor1)
+        assertThat(keyedObservable.addObserver(observer, executor1)).isTrue()
 
         keyedObservable.notifyChange(DataChangeReason.UPDATE)
-        keyedObservable.removeObserver(observer)
+        assertThat(keyedObservable.removeObserver(observer)).isTrue()
     }
 
     @Test
     fun notifyChange_KeyedObserver_addObserverWithinCallback() {
         // ConcurrentModificationException is raised if it is not implemented correctly
         val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ ->
-            keyedObservable.addObserver(key1, keyedObserver1, executor1)
+            assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue()
         }
 
-        keyedObservable.addObserver(key1, keyObserver, executor1)
+        assertThat(keyedObservable.addObserver(key1, keyObserver, executor1)).isTrue()
 
         keyedObservable.notifyChange(key1, DataChangeReason.UPDATE)
-        keyedObservable.removeObserver(key1, keyObserver)
+        assertThat(keyedObservable.removeObserver(key1, keyObserver)).isTrue()
     }
 }
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 4387b6f..a0599bb 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -35,6 +35,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.RawRes;
 import androidx.annotation.StringRes;
 import androidx.preference.Preference;
@@ -243,6 +244,14 @@
     }
 
     /**
+     * Gets the content description set by {@link #setContentDescription}.
+     */
+    @Nullable
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
      * Gets the lottie illustration resource id.
      */
     public int getLottieAnimationResId() {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 90cee16..1f3e2425 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -25,6 +25,13 @@
     val paddingLarge = 16.dp
     val paddingExtraLarge = 24.dp
 
+    val spinnerHorizontalPadding = paddingExtraLarge
+    val spinnerVerticalPadding = paddingLarge
+
+    val actionIconWidth = 32.dp
+    val actionIconHeight = 40.dp
+    val actionIconPadding = 4.dp
+
     val itemIconSize = 24.dp
     val itemIconContainerSize = 72.dp
     val itemPaddingStart = paddingExtraLarge
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 5f320f7..9bbc16d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -17,15 +17,24 @@
 package com.android.settingslib.spa.widget.scaffold
 
 import androidx.appcompat.R
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.Clear
 import androidx.compose.material.icons.outlined.FindInPage
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.res.stringResource
 import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 
 /** Action that navigates back to last page. */
 @Composable
@@ -50,6 +59,11 @@
         Icon(
             imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
             contentDescription = contentDescription,
+            modifier = if (isSpaExpressiveEnabled) Modifier
+                .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight)
+                .clip(SettingsShape.CornerExtraLarge)
+                .background(MaterialTheme.colorScheme.onSurfaceVariant)
+                .padding(SettingsDimension.actionIconPadding) else Modifier
         )
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 4cf741e..7d8ee79 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -39,6 +39,7 @@
 import com.android.settingslib.spa.framework.compose.horizontalValues
 import com.android.settingslib.spa.framework.compose.verticalValues
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 import com.android.settingslib.spa.framework.theme.settingsBackground
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -55,6 +56,10 @@
 ) {
     ActivityTitle(title)
     val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+    if (isSpaExpressiveEnabled) {
+        LaunchedEffect(scrollBehavior.state.heightOffsetLimit) { scrollBehavior.collapse() }
+    }
+
     Scaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index c48a147..6b2db90 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -46,6 +46,7 @@
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 
 data class SpinnerOption(
     val id: Int,
@@ -70,7 +71,10 @@
             )
             .selectableGroup(),
     ) {
-        val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
+        val contentPadding = if (isSpaExpressiveEnabled) PaddingValues(
+            horizontal = SettingsDimension.spinnerHorizontalPadding,
+            vertical = SettingsDimension.spinnerVerticalPadding
+        ) else PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
         Button(
             modifier = Modifier.semantics { role = Role.DropdownList },
             onClick = { expanded = true },
@@ -129,7 +133,11 @@
         text = option?.text ?: "",
         modifier = modifier
             .padding(end = SettingsDimension.itemPaddingEnd)
-            .padding(vertical = SettingsDimension.itemPaddingAround),
+            .then(
+                if (!isSpaExpressiveEnabled)
+                    Modifier.padding(vertical = SettingsDimension.itemPaddingAround)
+                else Modifier
+            ),
         color = color,
         style = MaterialTheme.typography.labelLarge,
     )
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 043219a..c686708 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -61,6 +61,10 @@
         mutableModesFlow.value += zenModes
     }
 
+    fun addMode(mode: ZenMode) {
+        mutableModesFlow.value += mode
+    }
+
     fun addMode(id: String, @AutomaticZenRule.Type type: Int = AutomaticZenRule.TYPE_UNKNOWN,
         active: Boolean = false) {
         mutableModesFlow.value += newMode(id, type, active)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index ca53fc2..3f3e1b2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -291,4 +291,12 @@
 
         assertThat(mPreference.isApplyDynamicColor()).isTrue();
     }
+
+    @Test
+    public void setContentDescription_getContentDescription_isEqual() {
+        final String contentDesc = "content desc";
+        mPreference.setContentDescription(contentDesc);
+
+        assertThat(mPreference.getContentDescription().toString()).isEqualTo(contentDesc);
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0b5187c..f3c5a18 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -941,6 +941,13 @@
     <!-- Permission required for CTS test - FileIntegrityManagerTest -->
     <uses-permission android:name="android.permission.SETUP_FSVERITY" />
 
+    <!-- Permissions required for CTS test - AppFunctionManagerTest -->
+    <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
+    <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />
+
+    <!-- Permission required for CTS test - CtsNfcTestCases -->
+    <uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant.xml
similarity index 89%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant.xml
index 9c3417f..6408a12 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant.xml
@@ -1,6 +1,6 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down.xml
similarity index 92%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down.xml
index a64a0d1..f13239c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_down.xml
@@ -1,6 +1,6 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up.xml
similarity index 95%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up.xml
index 40423c7..a5d15f9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_brightness_up.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock.xml
similarity index 90%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock.xml
index a0f7b5d..2127632 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_lock.xml
@@ -1,6 +1,6 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications.xml
similarity index 90%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications.xml
index 8757f22..62c8d1d 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_notifications.xml
@@ -1,6 +1,6 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power.xml
similarity index 90%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power.xml
index 049013a..ed11b44 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_power.xml
@@ -1,6 +1,6 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings.xml
similarity index 97%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings.xml
index 4f25e7d..2da63a6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_quick_settings.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps.xml
similarity index 94%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps.xml
index 38234c0..9763b8e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_recent_apps.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot.xml
similarity index 95%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot.xml
index 6d7f49c..2bfbd5b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_screenshot.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings.xml
similarity index 95%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings.xml
index 5ed6f19..4ca9bfc 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_settings.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down.xml
similarity index 94%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down.xml
index 16653e8..f924e5e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_down.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up.xml
similarity index 95%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up_24dp.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up.xml
index e572c6a..41fe351 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up_24dp.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_volume_up.xml
@@ -15,8 +15,8 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="108dp"
+    android:height="108dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="@color/colorControlNormal">
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml
new file mode 100644
index 0000000..6cab464
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/menuitem_background_ripple.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/ripple_material_color" />
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index 3c73eca..a1130e6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -12,7 +12,8 @@
       android:layout_height="@dimen/image_button_height"
       android:layout_alignParentTop="true"
       android:layout_centerHorizontal="true"
-      android:scaleType="fitCenter"/>
+      android:scaleType="fitCenter"
+      android:background="@drawable/menuitem_background_ripple" />
 
   <TextView
       android:id="@+id/shortcutLabel"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
index c698d18..11ce41e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
@@ -53,73 +53,73 @@
     /** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */
     private static final Map<ShortcutId, int[]> sShortcutResource = Map.ofEntries(
             Map.entry(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_assistant_24dp,
+                    R.drawable.ic_logo_a11y_assistant,
                     R.color.assistant_color,
                     R.string.assistant_utterance,
                     R.string.assistant_label,
             }),
             Map.entry(ShortcutId.ID_A11YSETTING_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_settings_24dp,
+                    R.drawable.ic_logo_a11y_settings,
                     R.color.a11y_settings_color,
                     R.string.a11y_settings_label,
                     R.string.a11y_settings_label,
             }),
             Map.entry(ShortcutId.ID_POWER_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_power_24dp,
+                    R.drawable.ic_logo_a11y_power,
                     R.color.power_color,
                     R.string.power_utterance,
                     R.string.power_label,
             }),
             Map.entry(ShortcutId.ID_RECENT_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_recent_apps_24dp,
+                    R.drawable.ic_logo_a11y_recent_apps,
                     R.color.recent_apps_color,
                     R.string.recent_apps_label,
                     R.string.recent_apps_label,
             }),
             Map.entry(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_lock_24dp,
+                    R.drawable.ic_logo_a11y_lock,
                     R.color.lockscreen_color,
                     R.string.lockscreen_label,
                     R.string.lockscreen_label,
             }),
             Map.entry(ShortcutId.ID_QUICKSETTING_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_quick_settings_24dp,
+                    R.drawable.ic_logo_a11y_quick_settings,
                     R.color.quick_settings_color,
                     R.string.quick_settings_label,
                     R.string.quick_settings_label,
             }),
             Map.entry(ShortcutId.ID_NOTIFICATION_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_notifications_24dp,
+                    R.drawable.ic_logo_a11y_notifications,
                     R.color.notifications_color,
                     R.string.notifications_label,
                     R.string.notifications_label,
             }),
             Map.entry(ShortcutId.ID_SCREENSHOT_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_screenshot_24dp,
+                    R.drawable.ic_logo_a11y_screenshot,
                     R.color.screenshot_color,
                     R.string.screenshot_utterance,
                     R.string.screenshot_label,
             }),
             Map.entry(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_brightness_up_24dp,
+                    R.drawable.ic_logo_a11y_brightness_up,
                     R.color.brightness_color,
                     R.string.brightness_up_label,
                     R.string.brightness_up_label,
             }),
             Map.entry(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_brightness_down_24dp,
+                    R.drawable.ic_logo_a11y_brightness_down,
                     R.color.brightness_color,
                     R.string.brightness_down_label,
                     R.string.brightness_down_label,
             }),
             Map.entry(ShortcutId.ID_VOLUME_UP_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_volume_up_24dp,
+                    R.drawable.ic_logo_a11y_volume_up,
                     R.color.volume_color,
                     R.string.volume_up_label,
                     R.string.volume_up_label,
             }),
             Map.entry(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] {
-                    R.drawable.ic_logo_a11y_volume_down_24dp,
+                    R.drawable.ic_logo_a11y_volume_down,
                     R.color.volume_color,
                     R.string.volume_down_label,
                     R.string.volume_down_label,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java
deleted file mode 100644
index 28ba4b5..0000000
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.accessibility.accessibilitymenu.utils;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RippleDrawable;
-
-import com.android.systemui.accessibility.accessibilitymenu.R;
-
-/** Creates background drawable for a11y menu shortcut. */
-public class ShortcutDrawableUtils {
-
-    /**
-     * To make the circular background of shortcut icons have higher resolution. The higher value of
-     * LENGTH is, the higher resolution of the circular background are.
-     */
-    private static final int LENGTH = 480;
-
-    private static final int RADIUS = LENGTH / 2;
-    private static final int COORDINATE = LENGTH / 2;
-    private static final int RIPPLE_COLOR_ID = R.color.ripple_material_color;
-
-    private final Context mContext;
-    private final ColorStateList mRippleColorStateList;
-
-    // Placeholder of drawable to prevent NullPointerException
-    private final ColorDrawable mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT);
-
-    public ShortcutDrawableUtils(Context context) {
-        this.mContext = context;
-
-        int rippleColor = context.getColor(RIPPLE_COLOR_ID);
-        mRippleColorStateList = ColorStateList.valueOf(rippleColor);
-    }
-
-    /**
-     * Creates a circular drawable in specific color for shortcut.
-     *
-     * @param colorResId color resource ID
-     * @return drawable circular drawable
-     */
-    public Drawable createCircularDrawable(int colorResId) {
-        Bitmap output = Bitmap.createBitmap(LENGTH, LENGTH, Config.ARGB_8888);
-        Canvas canvas = new Canvas(output);
-        int color = mContext.getColor(colorResId);
-        Paint paint = new Paint();
-        paint.setColor(color);
-        paint.setStrokeCap(Paint.Cap.ROUND);
-        paint.setStyle(Style.FILL);
-        canvas.drawCircle(COORDINATE, COORDINATE, RADIUS, paint);
-
-        BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(), output);
-        return drawable;
-    }
-
-    /**
-     * Creates an adaptive icon drawable in specific color for shortcut.
-     *
-     * @param colorResId color resource ID
-     * @return drawable for adaptive icon
-     */
-    public Drawable createAdaptiveIconDrawable(int colorResId) {
-        Drawable circleLayer = createCircularDrawable(colorResId);
-        RippleDrawable rippleLayer = new RippleDrawable(mRippleColorStateList, null, null);
-
-        AdaptiveIconDrawable adaptiveIconDrawable =
-                new AdaptiveIconDrawable(circleLayer, mTransparentDrawable);
-
-        Drawable[] layers = {adaptiveIconDrawable, rippleLayer};
-        LayerDrawable drawable = new LayerDrawable(layers);
-        return drawable;
-    }
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c333a7a..aa1bbbd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -16,7 +16,12 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
+import android.content.res.Resources;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.view.LayoutInflater;
 import android.view.TouchDelegate;
 import android.view.View;
@@ -26,11 +31,12 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
-import com.android.systemui.accessibility.accessibilitymenu.utils.ShortcutDrawableUtils;
 
 import java.util.List;
 
@@ -43,16 +49,12 @@
 
     private final AccessibilityMenuService mService;
     private final List<A11yMenuShortcut> mShortcutDataList;
-    private final ShortcutDrawableUtils mShortcutDrawableUtils;
 
     public A11yMenuAdapter(
             AccessibilityMenuService service,
             List<A11yMenuShortcut> shortcutDataList) {
         this.mService = service;
         this.mShortcutDataList = shortcutDataList;
-
-        mShortcutDrawableUtils = new ShortcutDrawableUtils(service);
-
         mLargeTextSize =
                 service.getResources().getDimensionPixelOffset(R.dimen.large_label_text_size);
     }
@@ -152,10 +154,10 @@
             shortcutIconButton.setContentDescription(
                     mService.getString(shortcutItem.imgContentDescription));
             shortcutLabel.setText(shortcutItem.labelText);
-            shortcutIconButton.setImageResource(shortcutItem.imageSrc);
 
-            shortcutIconButton.setBackground(
-                    mShortcutDrawableUtils.createAdaptiveIconDrawable(shortcutItem.imageColor));
+            AdaptiveIconDrawable iconDrawable = getAdaptiveIconDrawable(convertView,
+                    shortcutItem);
+            shortcutIconButton.setImageDrawable(iconDrawable);
 
             shortcutIconButton.setAccessibilityDelegate(new View.AccessibilityDelegate() {
                 @Override
@@ -167,4 +169,18 @@
             });
         }
     }
+
+    @NonNull
+    private static AdaptiveIconDrawable getAdaptiveIconDrawable(@NonNull View convertView,
+            @NonNull A11yMenuShortcut shortcutItem) {
+        Resources resources = convertView.getResources();
+        // Note: from the official guide, the foreground image of the adaptive icon should be
+        // sized at 108 x 108 dp
+        Drawable icon = resources.getDrawable(shortcutItem.imageSrc);
+        float inset = AdaptiveIconDrawable.getExtraInsetFraction();
+        AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
+                new ColorDrawable(resources.getColor(shortcutItem.imageColor)),
+                new InsetDrawable(icon, inset));
+        return iconDrawable;
+    }
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index 35f1248..b899c45 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -306,6 +306,10 @@
                     (viewPagerHeight - topMargin - defaultMargin
                             - (rowsInGridView * gridItemHeight))
                             / (rowsInGridView + 1);
+            // The interval is negative number when the viewPagerHeight is not able to fit
+            // the grid items, which result in text overlapping.
+            // Adjust the interval to 0 could solve the issue.
+            interval = Math.max(interval, 0);
             mViewPagerAdapter.setVerticalSpacing(interval);
 
             // Sets padding to view pager.
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d1a59af..892f778 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1396,3 +1396,9 @@
     }
 }
 
+flag {
+   name: "non_touchscreen_devices_bypass_falsing"
+   namespace: "systemui"
+   description: "Allow non-touchscreen devices to bypass falsing"
+   bug: "319809270"
+}
\ No newline at end of file
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 ed12776..a4dc8fc 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
@@ -216,7 +216,7 @@
 
 /** Scene containing the glanceable hub UI. */
 @Composable
-private fun SceneScope.CommunalScene(
+fun SceneScope.CommunalScene(
     backgroundType: CommunalBackgroundType,
     colors: CommunalColors,
     content: CommunalContent,
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 201c419..c63b29d 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
@@ -65,6 +65,7 @@
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.itemsIndexed
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.selection.selectable
@@ -136,6 +137,7 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
 import androidx.compose.ui.text.style.TextAlign
@@ -240,10 +242,15 @@
         }
     }
 
+    val paneTitle = stringResource(R.string.accessibility_content_description_for_communal_hub)
+
     Box(
         modifier =
             modifier
-                .semantics { testTagsAsResourceId = true }
+                .semantics {
+                    testTagsAsResourceId = true
+                    this.paneTitle = paneTitle
+                }
                 .testTag(COMMUNAL_HUB_TEST_TAG)
                 .fillMaxSize()
                 // Observe taps for selecting items
@@ -690,21 +697,20 @@
         horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
         verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
     ) {
-        items(
-            count = list.size,
-            key = { index -> list[index].key },
-            contentType = { index -> list[index].key },
-            span = { index -> GridItemSpan(list[index].size.span) },
-        ) { index ->
+        itemsIndexed(
+            items = list,
+            key = { _, item -> item.key },
+            contentType = { _, item -> item.key },
+            span = { _, item -> GridItemSpan(item.size.span) },
+        ) { index, item ->
             val size =
                 SizeF(
                     Dimensions.CardWidth.value,
-                    list[index].size.dp().value,
+                    item.size.dp().value,
                 )
             val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
             if (viewModel.isEditMode && dragDropState != null) {
-                val selected by
-                    remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
+                val selected = item.key == selectedKey.value
                 DraggableItem(
                     modifier =
                         if (dragDropState.draggingItemIndex == index) {
@@ -716,12 +722,12 @@
                         },
                     dragDropState = dragDropState,
                     selected = selected,
-                    enabled = list[index].isWidgetContent(),
+                    enabled = item.isWidgetContent(),
                     index = index,
                 ) { isDragging ->
                     CommunalContent(
                         modifier = cardModifier,
-                        model = list[index],
+                        model = item,
                         viewModel = viewModel,
                         size = size,
                         selected = selected && !isDragging,
@@ -734,7 +740,7 @@
                 }
             } else {
                 CommunalContent(
-                    model = list[index],
+                    model = item,
                     viewModel = viewModel,
                     size = size,
                     selected = false,
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 8b6de6a..e41a7df 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
@@ -17,20 +17,21 @@
 package com.android.systemui.communal.ui.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 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.view.layout.sections.CommunalAppWidgetSection
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
+import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
@@ -43,9 +44,8 @@
 @Inject
 constructor(
     private val viewModel: CommunalViewModel,
-    private val dialogFactory: SystemUIDialogFactory,
-    private val interactionHandler: WidgetInteractionHandler,
-    private val widgetSection: CommunalAppWidgetSection,
+    private val communalColors: CommunalColors,
+    private val communalContent: CommunalContent,
 ) : ExclusiveActivatable(), Scene {
     override val key = Scenes.Communal
 
@@ -63,12 +63,17 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        CommunalHub(
-            modifier = modifier,
+        val backgroundType by
+            viewModel.communalBackground.collectAsStateWithLifecycle(
+                initialValue = CommunalBackgroundType.ANIMATED
+            )
+
+        CommunalScene(
+            backgroundType = backgroundType,
+            colors = communalColors,
+            content = communalContent,
             viewModel = viewModel,
-            interactionHandler = interactionHandler,
-            widgetSection = widgetSection,
-            dialogFactory = dialogFactory,
+            modifier = modifier.horizontalNestedScrollToScene(),
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index ecb3d8c..c25a45d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.log.LongPressHandlingViewLogger
 import com.android.systemui.res.R
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -97,6 +98,7 @@
             Box {
                 DeviceEntryIcon(
                     viewModel = alternateBouncerDependencies.udfpsIconViewModel,
+                    logger = alternateBouncerDependencies.logger,
                     modifier =
                         Modifier.width { udfpsLocation.width }
                             .height { udfpsLocation.height }
@@ -151,13 +153,14 @@
 @Composable
 private fun DeviceEntryIcon(
     viewModel: AlternateBouncerUdfpsIconViewModel,
+    logger: LongPressHandlingViewLogger,
     modifier: Modifier = Modifier,
 ) {
     AndroidView(
         modifier = modifier,
         factory = { context ->
             val view =
-                DeviceEntryIconView(context, null).apply {
+                DeviceEntryIconView(context, null, logger = logger).apply {
                     id = R.id.alternate_bouncer_udfps_icon_view
                     contentDescription =
                         context.resources.getString(R.string.accessibility_fingerprint_label)
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 4129c25..a525f36 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,7 +18,6 @@
 
 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,9 +44,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
 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
@@ -66,7 +67,7 @@
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
     private val falsingManager: Lazy<FalsingManager>,
     private val vibratorHelper: Lazy<VibratorHelper>,
-    private val notificationPanelView: NotificationPanelView,
+    @LongPressTouchLog private val logBuffer: LogBuffer,
 ) {
     @Composable
     fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) {
@@ -74,29 +75,30 @@
             return
         }
 
-        notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let {
-            notificationPanelView.removeView(it)
-        }
-
         val context = LocalContext.current
 
         AndroidView(
             factory = { context ->
                 val view =
                     if (DeviceEntryUdfpsRefactor.isEnabled) {
-                        DeviceEntryIconView(context, null).apply {
-                            id = R.id.device_entry_icon_view
-                            DeviceEntryIconViewBinder.bind(
-                                applicationScope,
-                                this,
-                                deviceEntryIconViewModel.get(),
-                                deviceEntryForegroundViewModel.get(),
-                                deviceEntryBackgroundViewModel.get(),
-                                falsingManager.get(),
-                                vibratorHelper.get(),
-                                overrideColor,
+                        DeviceEntryIconView(
+                                context,
+                                null,
+                                logger = LongPressHandlingViewLogger(logBuffer, tag = TAG)
                             )
-                        }
+                            .apply {
+                                id = R.id.device_entry_icon_view
+                                DeviceEntryIconViewBinder.bind(
+                                    applicationScope,
+                                    this,
+                                    deviceEntryIconViewModel.get(),
+                                    deviceEntryForegroundViewModel.get(),
+                                    deviceEntryBackgroundViewModel.get(),
+                                    falsingManager.get(),
+                                    vibratorHelper.get(),
+                                    overrideColor,
+                                )
+                            }
                     } else {
                         // KeyguardBottomAreaRefactor.isEnabled
                         LockIconView(context, null).apply {
@@ -185,6 +187,10 @@
 
         return IntRect(center, radius)
     }
+
+    companion object {
+        private const val TAG = "LockSection"
+    }
 }
 
 private val LockIconElementKey = ElementKey("LockIcon")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index 4b3a39b..897a861 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -23,11 +23,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.unit.IntOffset
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
+import kotlin.math.max
 import kotlin.math.roundToInt
+import kotlin.math.tanh
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -36,6 +38,7 @@
     coroutineScope: CoroutineScope,
     canScrollForward: () -> Boolean
 ): Modifier {
+    val screenHeight = LocalRawScreenHeight.current
     val overscrollOffset = remember { Animatable(0f) }
     val stackNestedScrollConnection = remember {
         NotificationStackNestedScrollConnection(
@@ -43,7 +46,13 @@
             canScrollForward = canScrollForward,
             onScroll = { offsetAvailable ->
                 coroutineScope.launch {
-                    overscrollOffset.snapTo(overscrollOffset.value + offsetAvailable * 0.3f)
+                    val maxProgress = screenHeight * 0.2f
+                    val tilt = 3f
+                    var offset =
+                        overscrollOffset.value +
+                            maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
+                    offset = max(offset, -1f * maxProgress)
+                    overscrollOffset.snapTo(offset)
                 }
             },
             onStop = { velocityAvailable ->
@@ -79,13 +88,7 @@
             offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
         },
         canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
-        canContinueScroll = { source ->
-            if (source == NestedScrollSource.SideEffect) {
-                stackOffset() > STACK_OVERSCROLL_FLING_MIN_OFFSET
-            } else {
-                true
-            }
-        },
+        canContinueScroll = { stackOffset() > 0f },
         canScrollOnFling = true,
         onStart = { offsetAvailable -> onStart(offsetAvailable) },
         onScroll = { offsetAvailable ->
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 a2beba8..91ecfc1 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
@@ -667,4 +667,3 @@
 private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
 private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
 private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
-internal const val STACK_OVERSCROLL_FLING_MIN_OFFSET = -100f
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 5401936..826a255 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,14 +19,19 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.communal.ui.compose.AllElements
+import com.android.systemui.communal.ui.compose.Communal
 import com.android.systemui.scene.shared.model.Scenes
 
 fun TransitionBuilder.lockscreenToCommunalTransition() {
-    spec = tween(durationMillis = 500)
+    spec = tween(durationMillis = 1000)
 
-    // Translate lockscreen to the left.
+    // Translate lockscreen to the start direction.
     translate(Scenes.Lockscreen.rootElementKey, Edge.Start)
 
-    // Translate communal from the right.
-    translate(Scenes.Communal.rootElementKey, Edge.End)
+    // Translate communal hub grid from the end direction.
+    translate(Communal.Elements.Grid, Edge.End)
+
+    // Fade all communal hub elements.
+    timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 24fef71..f3577fa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -190,14 +190,12 @@
     private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
         val fromSource =
             startedPosition?.let { position ->
-                layoutImpl.swipeSourceDetector
-                    .source(
-                        layoutImpl.lastSize,
-                        position.round(),
-                        layoutImpl.density,
-                        orientation,
-                    )
-                    ?.resolve(layoutImpl.layoutDirection)
+                layoutImpl.swipeSourceDetector.source(
+                    layoutImpl.lastSize,
+                    position.round(),
+                    layoutImpl.density,
+                    orientation,
+                )
             }
 
         val upOrLeft =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index 97c0cef..edd697b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -54,23 +54,23 @@
         position: IntOffset,
         density: Density,
         orientation: Orientation,
-    ): Edge? {
+    ): Edge.Resolved? {
         val axisSize: Int
         val axisPosition: Int
-        val topOrLeft: Edge
-        val bottomOrRight: Edge
+        val topOrLeft: Edge.Resolved
+        val bottomOrRight: Edge.Resolved
         when (orientation) {
             Orientation.Horizontal -> {
                 axisSize = layoutSize.width
                 axisPosition = position.x
-                topOrLeft = Edge.Left
-                bottomOrRight = Edge.Right
+                topOrLeft = Edge.Resolved.Left
+                bottomOrRight = Edge.Resolved.Right
             }
             Orientation.Vertical -> {
                 axisSize = layoutSize.height
                 axisPosition = position.y
-                topOrLeft = Edge.Top
-                bottomOrRight = Edge.Bottom
+                topOrLeft = Edge.Resolved.Top
+                bottomOrRight = Edge.Resolved.Bottom
             }
         }
 
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 061613f..004bb40 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
@@ -379,6 +379,10 @@
         return this to UserActionResult(toScene = scene)
     }
 
+    infix fun to(overlay: OverlayKey): Pair<UserAction, UserActionResult> {
+        return this to UserActionResult(toOverlay = overlay)
+    }
+
     /** Resolve this into a [Resolved] user action given [layoutDirection]. */
     internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
 
@@ -475,7 +479,7 @@
         position: IntOffset,
         density: Density,
         orientation: Orientation,
-    ): SwipeSource?
+    ): SwipeSource.Resolved?
 }
 
 /** The result of performing a [UserAction]. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
index 3fda9b8..41b015a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
@@ -33,7 +33,7 @@
  * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
  * hub.
  */
-class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
+class CommunalSwipeDetector(private var lastDirection: SwipeSource.Resolved? = null) :
     SwipeSourceDetector, SwipeDetector {
     companion object {
         private const val TRAVEL_RATIO_THRESHOLD = .5f
@@ -44,15 +44,15 @@
         position: IntOffset,
         density: Density,
         orientation: Orientation
-    ): SwipeSource? {
+    ): SwipeSource.Resolved? {
         return lastDirection
     }
 
     override fun detectSwipe(change: PointerInputChange): Boolean {
         if (change.positionChange().x > 0) {
-            lastDirection = Edge.Left
+            lastDirection = Edge.Resolved.Left
         } else {
-            lastDirection = Edge.Right
+            lastDirection = Edge.Resolved.Right
         }
 
         // Determine whether the ratio of the distance traveled horizontally to the distance
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
index cceaf57..dea9283 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -34,7 +34,7 @@
 
     @Test
     fun horizontalEdges() {
-        fun horizontalEdge(position: Int): Edge? =
+        fun horizontalEdge(position: Int): Edge.Resolved? =
             detector.source(
                 layoutSize,
                 position = IntOffset(position, 0),
@@ -42,17 +42,17 @@
                 Orientation.Horizontal,
             )
 
-        assertThat(horizontalEdge(0)).isEqualTo(Edge.Left)
-        assertThat(horizontalEdge(30)).isEqualTo(Edge.Left)
+        assertThat(horizontalEdge(0)).isEqualTo(Edge.Resolved.Left)
+        assertThat(horizontalEdge(30)).isEqualTo(Edge.Resolved.Left)
         assertThat(horizontalEdge(31)).isEqualTo(null)
         assertThat(horizontalEdge(69)).isEqualTo(null)
-        assertThat(horizontalEdge(70)).isEqualTo(Edge.Right)
-        assertThat(horizontalEdge(100)).isEqualTo(Edge.Right)
+        assertThat(horizontalEdge(70)).isEqualTo(Edge.Resolved.Right)
+        assertThat(horizontalEdge(100)).isEqualTo(Edge.Resolved.Right)
     }
 
     @Test
     fun verticalEdges() {
-        fun verticalEdge(position: Int): Edge? =
+        fun verticalEdge(position: Int): Edge.Resolved? =
             detector.source(
                 layoutSize,
                 position = IntOffset(0, position),
@@ -60,11 +60,11 @@
                 Orientation.Vertical,
             )
 
-        assertThat(verticalEdge(0)).isEqualTo(Edge.Top)
-        assertThat(verticalEdge(30)).isEqualTo(Edge.Top)
+        assertThat(verticalEdge(0)).isEqualTo(Edge.Resolved.Top)
+        assertThat(verticalEdge(30)).isEqualTo(Edge.Resolved.Top)
         assertThat(verticalEdge(31)).isEqualTo(null)
         assertThat(verticalEdge(69)).isEqualTo(null)
-        assertThat(verticalEdge(70)).isEqualTo(Edge.Bottom)
-        assertThat(verticalEdge(100)).isEqualTo(Edge.Bottom)
+        assertThat(verticalEdge(70)).isEqualTo(Edge.Resolved.Bottom)
+        assertThat(verticalEdge(100)).isEqualTo(Edge.Resolved.Bottom)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
index 09aa2868..ca23228 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
@@ -17,11 +17,11 @@
 package com.android.systemui.accessibility.hearingaid;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.bluetooth.BluetoothDevice;
 import android.testing.TestableLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -51,6 +51,8 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    private static final int TEST_LAUNCH_SOURCE_ID = 1;
+
     private final FakeExecutor mMainExecutor = new FakeExecutor(new FakeSystemClock());
     private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
     @Mock
@@ -70,7 +72,7 @@
 
     @Before
     public void setUp() {
-        when(mDialogFactory.create(anyBoolean())).thenReturn(mDialogDelegate);
+        when(mDialogFactory.create(anyBoolean(), anyInt())).thenReturn(mDialogDelegate);
         when(mDialogDelegate.createDialog()).thenReturn(mDialog);
 
         mManager = new HearingDevicesDialogManager(
@@ -86,21 +88,22 @@
     public void showDialog_existHearingDevice_showPairNewDeviceFalse() {
         when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(true);
 
-        mManager.showDialog(mExpandable);
+        mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID);
         mBackgroundExecutor.runAllReady();
         mMainExecutor.runAllReady();
 
-        verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false));
+        verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false),
+                eq(TEST_LAUNCH_SOURCE_ID));
     }
 
     @Test
     public void showDialog_noHearingDevice_showPairNewDeviceTrue() {
         when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(false);
 
-        mManager.showDialog(mExpandable);
+        mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID);
         mBackgroundExecutor.runAllReady();
         mMainExecutor.runAllReady();
 
-        verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true));
+        verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true), eq(TEST_LAUNCH_SOURCE_ID));
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b7d99d2..65825b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -97,6 +97,8 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import dagger.Lazy;
 
 import org.junit.Before;
@@ -185,6 +187,8 @@
     private Resources mResources;
     @Mock
     private VibratorHelper mVibratorHelper;
+    @Mock
+    private MSDLPlayer mMSDLPlayer;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -1066,7 +1070,7 @@
                     () -> mLogContextInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
                     mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
-                    mLazyViewCapture);
+                    mLazyViewCapture, mMSDLPlayer);
         }
 
         @Override
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 65236f0..e3b5f34 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -31,6 +31,8 @@
 import com.android.systemui.coroutines.collectLastValue
 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.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -91,6 +93,8 @@
         kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
 
         kosmos.telecomManager = telecomManager
+
+        kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "")
     }
 
     @Test
@@ -130,6 +134,7 @@
             assertThat(metricsLogger.logs.element().category)
                 .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
             verify(activityTaskManager).stopSystemLockTaskMode()
+            assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
             verify(telecomManager).showInCallScreen(eq(false))
         }
 
@@ -156,6 +161,7 @@
             assertThat(metricsLogger.logs.element().category)
                 .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
             verify(activityTaskManager).stopSystemLockTaskMode()
+            assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
 
             // TODO(b/25189994): Test the activity has been started once we switch to the
             //  ActivityStarter interface here.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 53d82d7..956c129 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -95,6 +95,7 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -193,6 +194,13 @@
     }
 
     @Test
+    public void testSkipNonTouchscreenDevices() {
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+    }
+
+    @Test
     public void testTrackpadGesture() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
         when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 49c6239..df4b048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -25,13 +26,20 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManagerGlobal;
+import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.util.DisplayMetrics;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -56,11 +64,15 @@
     private FoldStateListener mFoldStateListener;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private DisplayMetrics mDisplayMetrics;
+    private IInputManager mIInputManager;
+    private InputManagerGlobal.TestSession inputManagerGlobalTestSession;
 
     @Before
     public void setup() {
         super.setup();
         MockitoAnnotations.initMocks(this);
+        mIInputManager = mock(IInputManager.Stub.class);
+        inputManagerGlobalTestSession = InputManagerGlobal.createTestSession(mIInputManager);
         mDisplayMetrics = new DisplayMetrics();
         mDisplayMetrics.xdpi = 100;
         mDisplayMetrics.ydpi = 100;
@@ -73,6 +85,7 @@
     public void tearDown() {
         super.tearDown();
         mDataProvider.onSessionEnd();
+        inputManagerGlobalTestSession.close();
     }
 
     @Test
@@ -378,6 +391,79 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void test_isTouchscreenSource_flagOff_alwaysTrue() {
+        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void test_isTouchscreenSource_recentEventsEmpty_true() {
+        //send no events into the data provider
+        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void test_isTouchscreenSource_latestDeviceTouchscreen_true() throws RemoteException {
+        int deviceId = 999;
+
+        InputDevice device = new InputDevice.Builder()
+                .setSources(InputDevice.SOURCE_CLASS_TRACKBALL | InputDevice.SOURCE_TOUCHSCREEN)
+                .setId(deviceId)
+                .build();
+        when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId});
+        when(mIInputManager.getInputDevice(anyInt())).thenReturn(device);
+
+        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
+                MotionEvent.PointerProperties.createArray(1),
+                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0,
+                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);
+
+        mDataProvider.onMotionEvent(event);
+        boolean result = mDataProvider.isTouchScreenSource();
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void test_isTouchscreenSource_latestDeviceNonTouchscreen_false() throws RemoteException {
+        int deviceId = 9999;
+
+        InputDevice device = new InputDevice.Builder()
+                .setSources(InputDevice.SOURCE_CLASS_TRACKBALL)
+                .setId(deviceId)
+                .build();
+        when(mIInputManager.getInputDeviceIds()).thenReturn(new int[]{deviceId});
+        when(mIInputManager.getInputDevice(anyInt())).thenReturn(device);
+
+        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
+                MotionEvent.PointerProperties.createArray(1),
+                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, deviceId, 0,
+                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);
+
+        mDataProvider.onMotionEvent(event);
+        boolean result = mDataProvider.isTouchScreenSource();
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+    public void test_isTouchscreenSource_latestDeviceNull_true() {
+        // Do not mock InputManager for this test
+        inputManagerGlobalTestSession.close();
+
+        int nonExistentDeviceId = 9997;
+        MotionEvent event = MotionEvent.obtain(1, 0, MotionEvent.ACTION_UP, 1,
+                MotionEvent.PointerProperties.createArray(1),
+                MotionEvent.PointerCoords.createArray(1), 0, 0, 1.0f, 1.0f, nonExistentDeviceId, 0,
+                InputDevice.SOURCE_CLASS_NONE, 0, 0, 0);
+
+        mDataProvider.onMotionEvent(event);
+        assertThat(mDataProvider.isTouchScreenSource()).isTrue();
+    }
+
+    @Test
     public void test_UnfoldedState_Folded() {
         FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
         when(mFoldStateListener.getFolded()).thenReturn(true);
@@ -413,7 +499,7 @@
     }
 
     private FalsingDataProvider createWithFoldCapability(boolean foldable) {
-        return new FalsingDataProvider(
-                mDisplayMetrics, mBatteryController, mFoldStateListener, mDockManager, foldable);
+        return new FalsingDataProvider(mDisplayMetrics, mBatteryController, mFoldStateListener,
+                mDockManager, foldable);
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
index bb400f2..f06cd6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
@@ -67,7 +67,8 @@
                 isAttachedToWindow = { isAttachedToWindow },
                 onLongPressDetected = onLongPressDetected,
                 onSingleTapDetected = onSingleTapDetected,
-                longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }
+                longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() },
+                allowedTouchSlop = ViewConfiguration.getTouchSlop(),
             )
         underTest.isLongPressHandlingEnabled = true
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt
index 4e58069..5bd3645 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
+import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
@@ -40,12 +41,16 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class IssueRecordingUserActionInteractorTest : SysuiTestCase() {
 
+    @Mock private lateinit var recordingController: RecordingController
+
     val user = UserHandle(1)
     val kosmos = Kosmos().also { it.testCase = this }
 
@@ -56,6 +61,7 @@
 
     @Before
     fun setup() {
+        MockitoAnnotations.initMocks(this)
         hasCreatedDialogDelegate = false
         with(kosmos) {
             val factory =
@@ -84,7 +90,8 @@
                     dialogTransitionAnimator,
                     panelInteractor,
                     userTracker,
-                    factory
+                    factory,
+                    recordingController,
                 )
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 91d8e2a..de3dc57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -25,7 +25,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
+import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
 import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -62,7 +64,12 @@
         context.orCreateTestableResources.apply {
             addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE)
             addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
-            addOverride(R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE)
+        }
+
+        val customPackageContext = SysuiTestableContext(context)
+        context.prepareCreatePackageContext(CUSTOM_PACKAGE, customPackageContext)
+        customPackageContext.orCreateTestableResources.apply {
+            addOverride(CUSTOM_DRAWABLE_ID, CUSTOM_DRAWABLE)
         }
     }
 
@@ -146,35 +153,41 @@
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
             assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
 
-            // Add an active mode: icon should be the mode icon. No iconResId, because we don't
-            // really know that it's a system icon.
+            // Add an active mode with a default icon: icon should be the mode icon, and the
+            // iconResId is also populated, because we know it's a system icon.
             zenModeRepository.addMode(
-                id = "Bedtime",
+                id = "Bedtime with default icon",
                 type = AutomaticZenRule.TYPE_BEDTIME,
                 active = true
             )
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
-            assertThat(tileData?.iconResId).isNull()
+            assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
 
-            // Add another, less-prioritized mode: icon should remain the first mode icon
+            // Add another, less-prioritized mode that has a *custom* icon: for now, icon should
+            // remain the first mode icon
             zenModeRepository.addMode(
-                id = "Driving",
-                type = AutomaticZenRule.TYPE_DRIVING,
-                active = true
+                TestModeBuilder()
+                    .setId("Driving with custom icon")
+                    .setType(AutomaticZenRule.TYPE_DRIVING)
+                    .setPackage(CUSTOM_PACKAGE)
+                    .setIconResId(CUSTOM_DRAWABLE_ID)
+                    .setActive(true)
+                    .build()
             )
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
-            assertThat(tileData?.iconResId).isNull()
+            assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
 
             // Deactivate more important mode: icon should be the less important, still active mode
-            zenModeRepository.deactivateMode("Bedtime")
+            // And because it's a package-provided icon, iconResId is not populated.
+            zenModeRepository.deactivateMode("Bedtime with default icon")
             runCurrent()
-            assertThat(tileData?.icon).isEqualTo(DRIVING_ICON)
+            assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON)
             assertThat(tileData?.iconResId).isNull()
 
             // Deactivate remaining mode: back to the default modes icon
-            zenModeRepository.deactivateMode("Driving")
+            zenModeRepository.deactivateMode("Driving with custom icon")
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
             assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
@@ -241,15 +254,17 @@
 
     private companion object {
         val TEST_USER = UserHandle.of(1)!!
+        const val CUSTOM_PACKAGE = "com.some.mode.owner.package"
 
         val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
+        const val CUSTOM_DRAWABLE_ID = 12345
 
         val MODES_DRAWABLE = TestStubDrawable("modes_icon")
         val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
-        val DRIVING_DRAWABLE = TestStubDrawable("driving")
+        val CUSTOM_DRAWABLE = TestStubDrawable("custom")
 
         val MODES_ICON = MODES_DRAWABLE.asIcon()
         val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon()
-        val DRIVING_ICON = DRIVING_DRAWABLE.asIcon()
+        val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index f7bdcb8..c3d45db 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.Flags
 import android.graphics.drawable.TestStubDrawable
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -109,26 +108,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_UI_ICONS)
-    fun state_withEnabledFlag_noIconResId() {
-        val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = false,
-                activeModes = emptyList(),
-                icon = icon,
-                iconResId = 123 // Should not be populated, but is ignored even if present
-            )
-
-        val state = underTest.map(config, model)
-
-        assertThat(state.icon()).isEqualTo(icon)
-        assertThat(state.iconRes).isNull()
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
-    fun state_withDisabledFlag_includesIconResId() {
+    fun state_modelHasIconResId_includesIconResId() {
         val icon = TestStubDrawable("res123").asIcon()
         val model =
             ModesTileModel(
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 ec79cc6..d180460 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
@@ -21,6 +21,8 @@
 import android.app.StatusBarManager
 import android.hardware.face.FaceManager
 import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -28,6 +30,8 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.internal.policy.IKeyguardDismissCallback
+import com.android.keyguard.AuthInteractionProperties
+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
@@ -48,6 +52,7 @@
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -95,6 +100,7 @@
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
+import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -137,6 +143,8 @@
     private val powerInteractor = kosmos.powerInteractor
     private val fakeTrustRepository = kosmos.fakeTrustRepository
     private val uiEventLoggerFake = kosmos.uiEventLoggerFake
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
+    private val authInteractionProperties = AuthInteractionProperties()
 
     private lateinit var underTest: SceneContainerStartable
 
@@ -654,6 +662,7 @@
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -680,6 +689,31 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_udfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasUdfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -707,6 +741,32 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps()
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playErrorHaptics_onFailedLockscreenAuth_udfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -727,6 +787,27 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playMSDLErrorHaptics_onFailedLockscreenAuth_udfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasUdfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playErrorHaptics_onFailedLockscreenAuth_sfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -747,6 +828,27 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playMSDLErrorHaptics_onFailedLockscreenAuth_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun skipsSuccessHaptics_whenPowerButtonDown_sfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -774,6 +876,32 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun skipsMSDLSuccessHaptics_whenPowerButtonDown_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps(isPowerButtonDown = true)
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -801,6 +929,32 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun skipsMSDLSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps(lastPowerPress = 50)
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun skipsErrorHaptics_whenPowerButtonDown_sfps() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -822,6 +976,28 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun skipsMSDLErrorHaptics_whenPowerButtonDown_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun skipsFaceErrorHaptics_nonSfps_coEx() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -842,6 +1018,26 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasUdfps = true, hasFace = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFaceAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
     fun hydrateSystemUiState() =
         testScope.runTest {
             val transitionStateFlow = prepareState()
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
index 59cfecc..e7a40d1 100644
--- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
+++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
@@ -16,7 +16,7 @@
 
 <com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/status_bar_latest_event_content"
+    android:id="@*android:id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_weight="1"
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
new file mode 100644
index 0000000..ca6d66a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@*android:id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:tag="big"
+    >
+
+    <LinearLayout
+        android:id="@*android:id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@*android:dimen/notification_content_margin"
+        android:orientation="vertical"
+        >
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="top"
+            >
+
+            <include layout="@*android:layout/notification_template_header" />
+
+            <LinearLayout
+                android:id="@*android:id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+                android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+                android:layout_marginTop="@*android:dimen/notification_content_margin_top"
+                android:orientation="vertical"
+                >
+
+                <include layout="@*android:layout/notification_template_part_line1" />
+
+                <include layout="@*android:layout/notification_template_text_multiline" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@*android:dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@*android:dimen/notification_progress_margin_top"
+                    layout="@*android:layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <include layout="@*android:layout/notification_template_right_icon" />
+        </FrameLayout>
+
+        <ViewStub
+            android:layout="@*android:layout/notification_material_reply_text"
+            android:id="@*android:id/notification_material_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@*android:layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+            android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+            android:layout_marginTop="@*android:dimen/notification_content_margin"
+            />
+
+        <include layout="@*android:layout/notification_material_action_list" />
+    </LinearLayout>
+</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index d08653c3..60edaae 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -52,7 +52,6 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.HapClientProfile;
@@ -105,7 +104,8 @@
     private final AudioManager mAudioManager;
     private final LocalBluetoothProfileManager mProfileManager;
     private final HapClientProfile mHapClientProfile;
-    private final UiEventLogger mUiEventLogger;
+    private final HearingDevicesUiEventLogger mUiEventLogger;
+    private final int mLaunchSourceId;
     private HearingDevicesListAdapter mDeviceListAdapter;
     private HearingDevicesPresetsController mPresetsController;
     private Context mApplicationContext;
@@ -153,20 +153,22 @@
     public interface Factory {
         /** Create a {@link HearingDevicesDialogDelegate} instance */
         HearingDevicesDialogDelegate create(
-                boolean showPairNewDevice);
+                boolean showPairNewDevice,
+                @HearingDevicesUiEventLogger.LaunchSourceId int launchSource);
     }
 
     @AssistedInject
     public HearingDevicesDialogDelegate(
             @Application Context applicationContext,
             @Assisted boolean showPairNewDevice,
+            @Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId,
             SystemUIDialog.Factory systemUIDialogFactory,
             ActivityStarter activityStarter,
             DialogTransitionAnimator dialogTransitionAnimator,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Main Handler handler,
             AudioManager audioManager,
-            UiEventLogger uiEventLogger) {
+            HearingDevicesUiEventLogger uiEventLogger) {
         mApplicationContext = applicationContext;
         mShowPairNewDevice = showPairNewDevice;
         mSystemUIDialogFactory = systemUIDialogFactory;
@@ -178,6 +180,7 @@
         mProfileManager = localBluetoothManager.getProfileManager();
         mHapClientProfile = mProfileManager.getHapClientProfile();
         mUiEventLogger = uiEventLogger;
+        mLaunchSourceId = launchSourceId;
     }
 
     @Override
@@ -191,7 +194,7 @@
 
     @Override
     public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
-        mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
+        mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, mLaunchSourceId);
         dismissDialogIfExists();
         Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
         Bundle bundle = new Bundle();
@@ -207,15 +210,17 @@
         CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice();
         switch (deviceItem.getType()) {
             case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE -> {
-                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
+                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT,
+                        mLaunchSourceId);
                 cachedBluetoothDevice.disconnect();
             }
             case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
-                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE);
+                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE,
+                        mLaunchSourceId);
                 cachedBluetoothDevice.setActive();
             }
             case SAVED_BLUETOOTH_DEVICE -> {
-                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT);
+                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT, mLaunchSourceId);
                 cachedBluetoothDevice.connect();
             }
         }
@@ -275,7 +280,7 @@
         if (mLocalBluetoothManager == null) {
             return;
         }
-        mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW);
+        mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
         mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
         mDeviceList = dialog.requireViewById(R.id.device_list);
         mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
@@ -363,7 +368,8 @@
         mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT);
+                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
+                        mLaunchSourceId);
                 mPresetsController.selectPreset(
                         mPresetsController.getAllPresetInfo().get(position).getIndex());
             }
@@ -381,7 +387,7 @@
     private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
         if (visibility == VISIBLE) {
             mPairButton.setOnClickListener(v -> {
-                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
+                mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId);
                 dismissDialogIfExists();
                 final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -485,7 +491,8 @@
             final String name = intent.getComponent() != null
                     ? intent.getComponent().flattenToString()
                     : intent.getPackage() + "/" + intent.getAction();
-            mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, 0, name);
+            mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK,
+                    mLaunchSourceId, name);
             dismissDialogIfExists();
             mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
                     mDialogTransitionAnimator.createActivityTransitionController(view));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
index bc4cb45..3d24177 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
@@ -70,8 +70,10 @@
      * Shows the dialog.
      *
      * @param expandable {@link Expandable} from which the dialog is shown.
+     * @param launchSourceId the id indicates where the dialog is launched from.
      */
-    public void showDialog(Expandable expandable) {
+    public void showDialog(Expandable expandable,
+            @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId) {
         if (mDialog != null) {
             if (DEBUG) {
                 Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first.");
@@ -91,7 +93,8 @@
                 });
         pairedHearingDeviceCheckTask.addListener(() -> {
             try {
-                mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get()).createDialog();
+                mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get(),
+                        launchSourceId).createDialog();
 
                 if (expandable != null) {
                     DialogTransitionAnimator.Controller controller =
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
index 6a34d19..02e65fd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.hearingaid;
 
+import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_A11Y;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -46,7 +48,7 @@
         }
 
         if (ACTION.equals(intent.getAction())) {
-            mDialogManager.showDialog(/* view= */ null);
+            mDialogManager.showDialog(/* expandable= */ null, LAUNCH_SOURCE_A11Y);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
deleted file mode 100644
index 3fbe56e..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
+++ /dev/null
@@ -1,51 +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.accessibility.hearingaid;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-
-public enum HearingDevicesUiEvent implements UiEventLogger.UiEventEnum {
-
-    @UiEvent(doc = "Hearing devices dialog is shown")
-    HEARING_DEVICES_DIALOG_SHOW(1848),
-    @UiEvent(doc = "Pair new device")
-    HEARING_DEVICES_PAIR(1849),
-    @UiEvent(doc = "Connect to the device")
-    HEARING_DEVICES_CONNECT(1850),
-    @UiEvent(doc = "Disconnect from the device")
-    HEARING_DEVICES_DISCONNECT(1851),
-    @UiEvent(doc = "Set the device as active device")
-    HEARING_DEVICES_SET_ACTIVE(1852),
-    @UiEvent(doc = "Click on the device gear to enter device detail page")
-    HEARING_DEVICES_GEAR_CLICK(1853),
-    @UiEvent(doc = "Select a preset from preset spinner")
-    HEARING_DEVICES_PRESET_SELECT(1854),
-    @UiEvent(doc = "Click on related tool")
-    HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
-
-    private final int mId;
-
-    HearingDevicesUiEvent(int id) {
-        mId = id;
-    }
-
-    @Override
-    public int getId() {
-        return mId;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
new file mode 100644
index 0000000..9e77b02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.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.accessibility.hearingaid
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+    @UiEvent(doc = "Hearing devices dialog is shown") HEARING_DEVICES_DIALOG_SHOW(1848),
+    @UiEvent(doc = "Pair new device") HEARING_DEVICES_PAIR(1849),
+    @UiEvent(doc = "Connect to the device") HEARING_DEVICES_CONNECT(1850),
+    @UiEvent(doc = "Disconnect from the device") HEARING_DEVICES_DISCONNECT(1851),
+    @UiEvent(doc = "Set the device as active device") HEARING_DEVICES_SET_ACTIVE(1852),
+    @UiEvent(doc = "Click on the device gear to enter device detail page")
+    HEARING_DEVICES_GEAR_CLICK(1853),
+    @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854),
+    @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+
+    override fun getId(): Int = this.id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt
new file mode 100644
index 0000000..0b32cfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.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.accessibility.hearingaid
+
+import android.annotation.IntDef
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class HearingDevicesUiEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
+
+    /** Logs the given event */
+    fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int) {
+        log(event, launchSourceId, null)
+    }
+
+    fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int, pkgName: String?) {
+        uiEventLogger.log(event, launchSourceId, pkgName)
+    }
+
+    /**
+     * The possible launch source of hearing devices dialog
+     *
+     * @hide
+     */
+    @IntDef(LAUNCH_SOURCE_UNKNOWN, LAUNCH_SOURCE_A11Y, LAUNCH_SOURCE_QS_TILE)
+    annotation class LaunchSourceId
+
+    companion object {
+        const val LAUNCH_SOURCE_UNKNOWN = 0
+        const val LAUNCH_SOURCE_A11Y = 1 // launch from AccessibilityManagerService
+        const val LAUNCH_SOURCE_QS_TILE = 2
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 970fdea..69ab976 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -78,6 +78,8 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import kotlin.Lazy;
 
 import kotlinx.coroutines.CoroutineScope;
@@ -157,6 +159,8 @@
 
     private final @Background DelayableExecutor mBackgroundExecutor;
 
+    private final MSDLPlayer mMSDLPlayer;
+
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
     // HAT received from LockSettingsService when credential is verified.
@@ -292,7 +296,8 @@
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull @Background DelayableExecutor bgExecutor,
             @NonNull VibratorHelper vibratorHelper,
-            Lazy<ViewCapture> lazyViewCapture) {
+            Lazy<ViewCapture> lazyViewCapture,
+            @NonNull MSDLPlayer msdlPlayer) {
         super(config.mContext);
 
         mConfig = config;
@@ -309,6 +314,7 @@
                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
         mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
         mBiometricCallback = new BiometricCallback();
+        mMSDLPlayer = msdlPlayer;
 
         final BiometricModalities biometricModalities = new BiometricModalities(
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
@@ -379,7 +385,7 @@
                 getJankListener(mLayout, TRANSIT,
                         BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
                 mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                vibratorHelper);
+                vibratorHelper, mMSDLPlayer);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 097ab72..b39aae9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -89,6 +89,8 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import dagger.Lazy;
 
 import kotlin.Unit;
@@ -183,6 +185,7 @@
     private final @Background DelayableExecutor mBackgroundExecutor;
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
     @NonNull private final VibratorHelper mVibratorHelper;
+    @NonNull private final MSDLPlayer mMSDLPlayer;
 
     private final kotlin.Lazy<ViewCapture> mLazyViewCapture;
 
@@ -742,7 +745,8 @@
             @Background DelayableExecutor bgExecutor,
             @NonNull UdfpsUtils udfpsUtils,
             @NonNull VibratorHelper vibratorHelper,
-            Lazy<ViewCapture> daggerLazyViewCapture) {
+            Lazy<ViewCapture> daggerLazyViewCapture,
+            @NonNull MSDLPlayer msdlPlayer) {
         mContext = context;
         mExecution = execution;
         mUserManager = userManager;
@@ -764,6 +768,7 @@
         mUdfpsUtils = udfpsUtils;
         mApplicationCoroutineScope = applicationCoroutineScope;
         mVibratorHelper = vibratorHelper;
+        mMSDLPlayer = msdlPlayer;
 
         mLogContextInteractor = logContextInteractor;
         mPromptSelectorInteractor = promptSelectorInteractorProvider;
@@ -1327,7 +1332,7 @@
                 wakefulnessLifecycle, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
                 mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
-                mLazyViewCapture);
+                mLazyViewCapture, mMSDLPlayer);
     }
 
     @Override
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 0b440ad..e7e8d8f 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
@@ -25,7 +25,6 @@
 import android.hardware.biometrics.Flags
 import android.hardware.face.FaceManager
 import android.util.Log
-import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -59,6 +58,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.domain.MSDLPlayer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
@@ -83,6 +83,7 @@
         legacyCallback: Spaghetti.Callback,
         applicationScope: CoroutineScope,
         vibratorHelper: VibratorHelper,
+        msdlPlayer: MSDLPlayer,
     ): Spaghetti {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
 
@@ -434,21 +435,27 @@
                 // Play haptics
                 launch {
                     viewModel.hapticsToPlay.collect { haptics ->
-                        if (haptics.hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
-                            if (haptics.flag != null) {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    haptics.hapticFeedbackConstant,
-                                    haptics.flag,
-                                )
-                            } else {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    haptics.hapticFeedbackConstant,
-                                )
+                        when (haptics) {
+                            is PromptViewModel.HapticsToPlay.HapticConstant -> {
+                                if (haptics.flag != null) {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        haptics.constant,
+                                        haptics.flag,
+                                    )
+                                } else {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        haptics.constant,
+                                    )
+                                }
                             }
-                            viewModel.clearHaptics()
+                            is PromptViewModel.HapticsToPlay.MSDL -> {
+                                msdlPlayer.playToken(haptics.token, haptics.properties)
+                            }
+                            is PromptViewModel.HapticsToPlay.None -> {}
                         }
+                        viewModel.clearHaptics()
                     }
                 }
 
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 85c3ae3..d69e875 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
@@ -18,13 +18,11 @@
 
 import android.animation.Animator
 import android.animation.AnimatorSet
-import android.animation.ValueAnimator
 import android.graphics.Outline
 import android.graphics.Rect
 import android.transition.AutoTransition
 import android.transition.TransitionManager
 import android.util.TypedValue
-import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewOutlineProvider
@@ -413,13 +411,12 @@
                                     ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
                                 )
 
-                                TransitionManager.beginDelayedTransition(view, autoTransition)
-
                                 if (position.isLeft) {
                                     flipConstraintSet.applyTo(view)
                                 } else {
                                     mediumConstraintSet.applyTo(view)
                                 }
+                                TransitionManager.beginDelayedTransition(view, autoTransition)
                             }
                             size.isMedium -> {
                                 if (position.isLeft) {
@@ -428,14 +425,18 @@
                                     mediumConstraintSet.applyTo(view)
                                 }
                             }
-                            size.isLarge && currentSize.isMedium -> {
+                            size.isLarge -> {
                                 val autoTransition = AutoTransition()
                                 autoTransition.setDuration(
-                                    ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+                                    if (currentSize.isSmall) {
+                                        ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
+                                    } else {
+                                        ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+                                    }
                                 )
 
-                                TransitionManager.beginDelayedTransition(view, autoTransition)
                                 largeConstraintSet.applyTo(view)
+                                TransitionManager.beginDelayedTransition(view, autoTransition)
                             }
                         }
 
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 4c2fe07..168ba11 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
@@ -35,7 +35,9 @@
 import android.util.RotationUtils
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
+import com.android.keyguard.AuthInteractionProperties
 import com.android.launcher3.icons.IconProvider
+import com.android.systemui.Flags.msdlFeedback
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.Utils.isSystem
@@ -53,6 +55,8 @@
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.res.R
 import com.android.systemui.util.kotlin.combine
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
 import javax.inject.Inject
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
@@ -245,8 +249,9 @@
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
-    private val _hapticsToPlay =
-        MutableStateFlow(HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, /* flag= */ null))
+    private val authInteractionProperties = AuthInteractionProperties()
+    private val _hapticsToPlay: MutableStateFlow<HapticsToPlay> =
+        MutableStateFlow(HapticsToPlay.None)
 
     /** Event fired to the view indicating a [HapticsToPlay] */
     val hapticsToPlay = _hapticsToPlay.asStateFlow()
@@ -939,26 +944,52 @@
     }
 
     private fun vibrateOnSuccess() {
-        _hapticsToPlay.value =
-            HapticsToPlay(
-                HapticFeedbackConstants.BIOMETRIC_CONFIRM,
-                null,
-            )
+        val haptics =
+            if (msdlFeedback()) {
+                HapticsToPlay.MSDL(MSDLToken.UNLOCK, authInteractionProperties)
+            } else {
+                HapticsToPlay.HapticConstant(
+                    HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+                    flag = null,
+                )
+            }
+        _hapticsToPlay.value = haptics
     }
 
     private fun vibrateOnError() {
-        _hapticsToPlay.value =
-            HapticsToPlay(
-                HapticFeedbackConstants.BIOMETRIC_REJECT,
-                null,
-            )
+        val haptics =
+            if (msdlFeedback()) {
+                HapticsToPlay.MSDL(MSDLToken.FAILURE, authInteractionProperties)
+            } else {
+                HapticsToPlay.HapticConstant(
+                    HapticFeedbackConstants.BIOMETRIC_REJECT,
+                    flag = null,
+                )
+            }
+        _hapticsToPlay.value = haptics
     }
 
     /** Clears the [hapticsToPlay] variable by setting its constant to the NO_HAPTICS default. */
     fun clearHaptics() {
-        _hapticsToPlay.update { previous ->
-            HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, previous.flag)
-        }
+        _hapticsToPlay.update { HapticsToPlay.None }
+    }
+
+    /** The state of haptic feedback to play. */
+    sealed interface HapticsToPlay {
+        /**
+         * Haptics using [HapticFeedbackConstants]. It is composed by a [HapticFeedbackConstants]
+         * and a [HapticFeedbackConstants] flag.
+         */
+        data class HapticConstant(val constant: Int, val flag: Int?) : HapticsToPlay
+
+        /**
+         * Haptics using MSDL feedback. It is composed by a [MSDLToken] and optional
+         * [InteractionProperties]
+         */
+        data class MSDL(val token: MSDLToken, val properties: InteractionProperties?) :
+            HapticsToPlay
+
+        data object None : HapticsToPlay
     }
 
     companion object {
@@ -1095,9 +1126,3 @@
     val isStarted: Boolean
         get() = this == Normal || this == Delayed
 }
-
-/**
- * The state of haptic feedback to play. It is composed by a [HapticFeedbackConstants] and a
- * [HapticFeedbackConstants] flag.
- */
-data class HapticsToPlay(val hapticFeedbackConstant: Int, val flag: Int?)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
index f36ef66..8b5a09b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
@@ -34,10 +34,14 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.doze.DozeLogger
+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.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.EmergencyDialerConstants
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -69,6 +73,7 @@
     private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory,
     private val metricsLogger: MetricsLogger,
     private val dozeLogger: DozeLogger,
+    private val sceneInteractor: Lazy<SceneInteractor>,
 ) {
     /** The bouncer action button. If `null`, the button should not be shown. */
     val actionButton: Flow<BouncerActionButtonModel?> =
@@ -158,14 +163,17 @@
     }
 
     private fun prepareToPerformAction() {
-        // TODO(b/308001302): Trigger occlusion and resetting bouncer state.
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor.get().changeScene(Scenes.Lockscreen, "Bouncer action button clicked")
+        }
+
         metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL)
         activityTaskManager.stopSystemLockTaskMode()
     }
 
     @SuppressLint("MissingPermission")
     private fun returnToCall() {
-        telecomManager?.showInCallScreen(/* showDialpad = */ false)
+        telecomManager?.showInCallScreen(/* showDialpad= */ false)
     }
 
     private val <T> Flow<T>.asUnitFlow: Flow<Unit>
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e1ba93c..83d4091 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -34,8 +34,6 @@
 import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
 import com.android.systemui.classifier.HistoryTracker.BeliefListener;
 import com.android.systemui.dagger.qualifiers.TestHarness;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -396,6 +394,7 @@
                 || mDataProvider.isA11yAction()
                 || mDataProvider.isFromTrackpad()
                 || mDataProvider.isFromKeyboard()
+                || !mDataProvider.isTouchScreenSource()
                 || mDataProvider.isUnfolded();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 2eca02c..962ab99 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -28,6 +29,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Flags;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -343,7 +345,9 @@
         // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
         // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
         // any other events are processed, otherwise the whole gesture will be recorded.
-        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+        //
+        // We should only delay processing of these events for touchscreen sources
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN && isTouchscreenSource(ev)) {
             // Make a copy of ev, since it will be recycled after we exit this method.
             mPendingDownEvent = MotionEvent.obtain(ev);
             mAvoidGesture = false;
@@ -410,6 +414,22 @@
         mFalsingDataProvider.onA11yAction();
     }
 
+    /**
+     * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to
+     * {@code true} if the device is {@code null}
+     */
+    private boolean isTouchscreenSource(MotionEvent ev) {
+        if (!Flags.nonTouchscreenDevicesBypassFalsing()) {
+            return true;
+        }
+        InputDevice device = ev.getDevice();
+        if (device != null) {
+            return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
+        } else {
+            return true;
+        }
+    }
+
     private boolean shouldSessionBeActive() {
         return mScreenOn
                 && (mState == StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 1501701..769976e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,11 +20,13 @@
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.util.DisplayMetrics;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -281,6 +283,9 @@
     }
 
     public boolean isFromTrackpad() {
+        if (Flags.nonTouchscreenDevicesBypassFalsing()) {
+            return false;
+        }
         if (mRecentMotionEvents.isEmpty()) {
             return false;
         }
@@ -290,6 +295,25 @@
                 || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
+    /**
+     * returns {@code true} if the device supports Touchscreen, {@code false} otherwise. Defaults to
+     * {@code true} if the device is {@code null}
+     */
+    public boolean isTouchScreenSource() {
+        if (!Flags.nonTouchscreenDevicesBypassFalsing()) {
+            return true;
+        }
+        if (mRecentMotionEvents.isEmpty()) {
+            return true;
+        }
+        InputDevice device = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getDevice();
+        if (device != null) {
+            return device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
+        } else {
+            return true;
+        }
+    }
+
     private void recalculateData() {
         if (!mDirty) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index b6ace81..9c4736a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -27,6 +27,7 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.systemui.log.LongPressHandlingViewLogger
 import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
@@ -42,6 +43,8 @@
     context: Context,
     attrs: AttributeSet?,
     longPressDuration: () -> Long,
+    allowedTouchSlop: Int = ViewConfiguration.getTouchSlop(),
+    logger: LongPressHandlingViewLogger? = null,
 ) :
     View(
         context,
@@ -97,6 +100,8 @@
             },
             onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
             longPressDuration = longPressDuration,
+            allowedTouchSlop = allowedTouchSlop,
+            logger = logger,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
index d3fc610..4e38a49 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
@@ -17,7 +17,7 @@
 
 package com.android.systemui.common.ui.view
 
-import android.view.ViewConfiguration
+import com.android.systemui.log.LongPressHandlingViewLogger
 import kotlinx.coroutines.DisposableHandle
 
 /** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */
@@ -35,6 +35,14 @@
     private val onSingleTapDetected: () -> Unit,
     /** Time for the touch to be considered a long-press in ms */
     var longPressDuration: () -> Long,
+    /**
+     * Default touch slop that is allowed, if the movement between [MotionEventModel.Down] and
+     * [MotionEventModel.Up] is more than [allowedTouchSlop] then the touch is not processed as
+     * single tap or a long press.
+     */
+    val allowedTouchSlop: Int,
+    /** Optional logger that can be passed in to log touch events */
+    val logger: LongPressHandlingViewLogger? = null,
 ) {
     sealed class MotionEventModel {
         object Other : MotionEventModel()
@@ -70,22 +78,26 @@
                 true
             }
             is MotionEventModel.Move -> {
-                if (event.distanceMoved > ViewConfiguration.getTouchSlop()) {
+                if (event.distanceMoved > allowedTouchSlop) {
+                    logger?.cancelingLongPressDueToTouchSlop(event.distanceMoved, allowedTouchSlop)
                     cancelScheduledLongPress()
                 }
                 false
             }
             is MotionEventModel.Up -> {
+                logger?.onUpEvent(event.distanceMoved, allowedTouchSlop, event.gestureDuration)
                 cancelScheduledLongPress()
                 if (
-                    event.distanceMoved <= ViewConfiguration.getTouchSlop() &&
+                    event.distanceMoved <= allowedTouchSlop &&
                         event.gestureDuration < longPressDuration()
                 ) {
+                    logger?.dispatchingSingleTap()
                     dispatchSingleTap()
                 }
                 false
             }
             is MotionEventModel.Cancel -> {
+                logger?.motionEventCancelled()
                 cancelScheduledLongPress()
                 false
             }
@@ -97,15 +109,18 @@
         x: Int,
         y: Int,
     ) {
+        val duration = longPressDuration()
+        logger?.schedulingLongPress(duration)
         scheduledLongPressHandle =
             postDelayed(
                 {
+                    logger?.longPressTriggered()
                     dispatchLongPress(
                         x = x,
                         y = y,
                     )
                 },
-                longPressDuration(),
+                duration,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
index e00e33d..43aab35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
@@ -39,12 +39,14 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -75,62 +77,66 @@
     private val disableToken: IBinder = Binder()
 
     private val disableFlagsForUserId =
-        combine(
-                selectedUserInteractor.selectedUser,
-                keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to },
-                deviceConfigInteractor.property(
-                    namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
-                    name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
-                    default = true,
-                ),
-                navigationInteractor.isGesturalMode,
-                authenticationInteractor.authenticationMethod,
-                powerInteractor.detailedWakefulness,
-            ) { values ->
-                val selectedUserId = values[0] as Int
-                val startedState = values[1] as KeyguardState
-                val isShowHomeOverLockscreen = values[2] as Boolean
-                val isGesturalMode = values[3] as Boolean
-                val authenticationMethod = values[4] as AuthenticationMethodModel
-                val wakefulnessModel = values[5] as WakefulnessModel
-                val isOccluded = startedState == KeyguardState.OCCLUDED
+        if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) {
+            flowOf(Pair(0, StatusBarManager.DISABLE_NONE))
+        } else {
+            combine(
+                    selectedUserInteractor.selectedUser,
+                    keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to },
+                    deviceConfigInteractor.property(
+                        namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
+                        name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+                        default = true,
+                    ),
+                    navigationInteractor.isGesturalMode,
+                    authenticationInteractor.authenticationMethod,
+                    powerInteractor.detailedWakefulness,
+                ) { values ->
+                    val selectedUserId = values[0] as Int
+                    val startedState = values[1] as KeyguardState
+                    val isShowHomeOverLockscreen = values[2] as Boolean
+                    val isGesturalMode = values[3] as Boolean
+                    val authenticationMethod = values[4] as AuthenticationMethodModel
+                    val wakefulnessModel = values[5] as WakefulnessModel
+                    val isOccluded = startedState == KeyguardState.OCCLUDED
 
-                val hideHomeAndRecentsForBouncer =
-                    startedState == KeyguardState.PRIMARY_BOUNCER ||
-                        startedState == KeyguardState.ALTERNATE_BOUNCER
-                val isKeyguardShowing = startedState != KeyguardState.GONE
-                val isPowerGestureIntercepted =
-                    with(wakefulnessModel) {
-                        isAwake() &&
-                            powerButtonLaunchGestureTriggered &&
-                            lastSleepReason == WakeSleepReason.POWER_BUTTON
+                    val hideHomeAndRecentsForBouncer =
+                        startedState == KeyguardState.PRIMARY_BOUNCER ||
+                            startedState == KeyguardState.ALTERNATE_BOUNCER
+                    val isKeyguardShowing = startedState != KeyguardState.GONE
+                    val isPowerGestureIntercepted =
+                        with(wakefulnessModel) {
+                            isAwake() &&
+                                powerButtonLaunchGestureTriggered &&
+                                lastSleepReason == WakeSleepReason.POWER_BUTTON
+                        }
+
+                    var flags = StatusBarManager.DISABLE_NONE
+
+                    if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) {
+                        if (!isShowHomeOverLockscreen || !isGesturalMode) {
+                            flags = flags or StatusBarManager.DISABLE_HOME
+                        }
+                        flags = flags or StatusBarManager.DISABLE_RECENT
                     }
 
-                var flags = StatusBarManager.DISABLE_NONE
-
-                if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) {
-                    if (!isShowHomeOverLockscreen || !isGesturalMode) {
-                        flags = flags or StatusBarManager.DISABLE_HOME
+                    if (
+                        isPowerGestureIntercepted &&
+                            isOccluded &&
+                            authenticationMethod.isSecure &&
+                            deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+                    ) {
+                        flags = flags or StatusBarManager.DISABLE_RECENT
                     }
-                    flags = flags or StatusBarManager.DISABLE_RECENT
-                }
 
-                if (
-                    isPowerGestureIntercepted &&
-                        isOccluded &&
-                        authenticationMethod.isSecure &&
-                        deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
-                ) {
-                    flags = flags or StatusBarManager.DISABLE_RECENT
+                    selectedUserId to flags
                 }
-
-                selectedUserId to flags
-            }
-            .distinctUntilChanged()
+                .distinctUntilChanged()
+        }
 
     @SuppressLint("WrongConstant", "NonInjectedService")
     override fun start() {
-        if (!KeyguardWmStateRefactor.isEnabled) {
+        if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 91a7f7f..7696273 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LongPressHandlingViewLogger
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scrim.ScrimView
@@ -191,6 +192,7 @@
 
         optionallyAddUdfpsViews(
             view = view,
+            logger = alternateBouncerDependencies.logger,
             udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel,
             udfpsA11yOverlayViewModel =
                 alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel,
@@ -248,6 +250,7 @@
 
     private fun optionallyAddUdfpsViews(
         view: ConstraintLayout,
+        logger: LongPressHandlingViewLogger,
         udfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
         udfpsA11yOverlayViewModel: Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>,
     ) {
@@ -276,7 +279,7 @@
                         var udfpsView = view.getViewById(udfpsViewId)
                         if (udfpsView == null) {
                             udfpsView =
-                                DeviceEntryIconView(view.context, null).apply {
+                                DeviceEntryIconView(view.context, null, logger = logger).apply {
                                     id = udfpsViewId
                                     contentDescription =
                                         context.resources.getString(
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 4d6577c..b951b73 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
@@ -19,6 +19,7 @@
 
 import android.annotation.SuppressLint
 import android.content.res.ColorStateList
+import android.util.Log
 import android.util.StateSet
 import android.view.HapticFeedbackConstants
 import android.view.View
@@ -83,6 +84,11 @@
                     if (
                         !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
                     ) {
+                        Log.d(
+                            TAG,
+                            "Long press rejected because it is not a11yAction " +
+                                "and it is a falseLongTap"
+                        )
                         return
                     }
                     vibratorHelper.performHapticFeedback(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 3e6d5da..8d2e939 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -23,6 +23,7 @@
 import android.util.StateSet
 import android.view.Gravity
 import android.view.View
+import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.FrameLayout
@@ -31,6 +32,7 @@
 import com.airbnb.lottie.LottieCompositionFactory
 import com.airbnb.lottie.LottieDrawable
 import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.log.LongPressHandlingViewLogger
 import com.android.systemui.res.R
 
 class DeviceEntryIconView
@@ -39,8 +41,17 @@
     context: Context,
     attrs: AttributeSet?,
     defStyleAttrs: Int = 0,
+    logger: LongPressHandlingViewLogger? = null,
 ) : FrameLayout(context, attrs, defStyleAttrs) {
-    val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs)
+
+    val longPressHandlingView: LongPressHandlingView =
+        LongPressHandlingView(
+            context = context,
+            attrs = attrs,
+            longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() },
+            allowedTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(),
+            logger = logger,
+        )
     val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg }
     val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg }
     val aodFpDrawable: LottieDrawable = LottieDrawable()
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 51230dd..782d37b 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
@@ -42,6 +42,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -69,6 +72,7 @@
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
     private val falsingManager: Lazy<FalsingManager>,
     private val vibratorHelper: Lazy<VibratorHelper>,
+    @LongPressTouchLog private val logBuffer: LogBuffer,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
     private var disposableHandle: DisposableHandle? = null
@@ -88,7 +92,16 @@
 
         val view =
             if (DeviceEntryUdfpsRefactor.isEnabled) {
-                DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
+                DeviceEntryIconView(
+                        context,
+                        null,
+                        logger =
+                            LongPressHandlingViewLogger(
+                                logBuffer = logBuffer,
+                                TAG
+                            )
+                    )
+                    .apply { id = deviceEntryIconViewId }
             } else {
                 // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled
                 LockIconView(context, null).apply { id = R.id.lock_icon_view }
@@ -258,4 +271,8 @@
             }
         }
     }
+
+    companion object {
+        private const val TAG = "DefaultDeviceEntrySection"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
index b432417..9f8e9c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt
@@ -18,6 +18,9 @@
 
 import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
 import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import dagger.Lazy
@@ -37,4 +40,11 @@
         Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>,
     val messageAreaViewModel: AlternateBouncerMessageAreaViewModel,
     val powerInteractor: PowerInteractor,
-)
+    @LongPressTouchLog private val touchLogBuffer: LogBuffer,
+) {
+    val logger: LongPressHandlingViewLogger =
+        LongPressHandlingViewLogger(logBuffer = touchLogBuffer, TAG)
+    companion object {
+        private const val TAG = "AlternateBouncer"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt
new file mode 100644
index 0000000..4ff8118
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.google.errorprone.annotations.CompileTimeConstant
+
+data class LongPressHandlingViewLogger
+constructor(
+    private val logBuffer: LogBuffer,
+    @CompileTimeConstant private val tag: String = "LongPressHandlingViewLogger"
+) {
+    fun schedulingLongPress(delay: Long) {
+        logBuffer.log(
+            tag,
+            DEBUG,
+            { long1 = delay },
+            { "on MotionEvent.Down: scheduling long press activation after $long1 ms" }
+        )
+    }
+
+    fun longPressTriggered() {
+        logBuffer.log(tag, DEBUG, "long press event detected and dispatched")
+    }
+
+    fun motionEventCancelled() {
+        logBuffer.log(tag, DEBUG, "Long press may be cancelled due to MotionEventModel.Cancel")
+    }
+
+    fun dispatchingSingleTap() {
+        logBuffer.log(tag, DEBUG, "Dispatching single tap instead of long press")
+    }
+
+    fun onUpEvent(distanceMoved: Float, touchSlop: Int, gestureDuration: Long) {
+        logBuffer.log(
+            tag,
+            DEBUG,
+            {
+                double1 = distanceMoved.toDouble()
+                int1 = touchSlop
+                long1 = gestureDuration
+            },
+            {
+                "on MotionEvent.Up: distanceMoved: $double1, " +
+                    "allowedTouchSlop: $int1, " +
+                    "eventDuration: $long1"
+            }
+        )
+    }
+
+    fun cancelingLongPressDueToTouchSlop(distanceMoved: Float, allowedTouchSlop: Int) {
+        logBuffer.log(
+            tag,
+            DEBUG,
+            {
+                double1 = distanceMoved.toDouble()
+                int1 = allowedTouchSlop
+            },
+            {
+                "on MotionEvent.Motion: May cancel long press due to movement: " +
+                    "distanceMoved: $double1, " +
+                    "allowedTouchSlop: $int1 "
+            }
+        )
+    }
+}
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 19906fd..498c34c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -717,4 +717,12 @@
     public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) {
         return factory.create("VolumeLog", 50);
     }
+
+    /** Provides a {@link LogBuffer} for use by long touch event handlers. */
+    @Provides
+    @SysUISingleton
+    @LongPressTouchLog
+    public static LogBuffer providesLongPressTouchLog(LogBufferFactory factory) {
+        return factory.create("LongPressViewLog", 200);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt
new file mode 100644
index 0000000..1163d74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** Log buffer for logging touch/long press events */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class LongPressTouchLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
index b96e83d..f723ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE;
+
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
@@ -96,7 +98,7 @@
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
-        mUiHandler.post(() -> mDialogManager.showDialog(expandable));
+        mUiHandler.post(() -> mDialogManager.showDialog(expandable, LAUNCH_SOURCE_QS_TILE));
     }
 
     @Override
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 a3feb2b..d89e73d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -43,12 +43,14 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.recordissue.IssueRecordingService
+import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent
+import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent
 import com.android.systemui.recordissue.IssueRecordingState
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
 import com.android.systemui.recordissue.TraceurMessageSender
 import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.RecordingService
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
@@ -56,6 +58,9 @@
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
+const val DELAY_MS: Long = 0
+const val INTERVAL_MS: Long = 1000
+
 class RecordIssueTile
 @Inject
 constructor(
@@ -77,6 +82,7 @@
     @Background private val bgExecutor: Executor,
     private val issueRecordingState: IssueRecordingState,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
+    private val recordingController: RecordingController,
 ) :
     QSTileImpl<QSTile.BooleanState>(
         host,
@@ -132,23 +138,25 @@
     }
 
     private fun startIssueRecordingService() =
-        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())
+        recordingController.startCountdown(
+            DELAY_MS,
+            INTERVAL_MS,
+            pendingServiceIntent(getStartIntent(userContextProvider.userContext)),
+            pendingServiceIntent(getStopIntent(userContextProvider.userContext))
+        )
 
     private fun stopIssueRecordingService() =
-        PendingIntent.getService(
-                userContextProvider.userContext,
-                RecordingService.REQUEST_CODE,
-                IssueRecordingService.getStopIntent(userContextProvider.userContext),
-                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
-            )
+        pendingServiceIntent(getStopIntent(userContextProvider.userContext))
             .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
 
+    private fun pendingServiceIntent(action: Intent) =
+        PendingIntent.getService(
+            userContextProvider.userContext,
+            RecordingService.REQUEST_CODE,
+            action,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )
+
     private fun showPrompt(expandable: Expandable?) {
         val dialog: AlertDialog =
             delegateFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt
index 4971fef..0c8a375 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt
@@ -19,6 +19,7 @@
 import android.app.AlertDialog
 import android.app.BroadcastOptions
 import android.app.PendingIntent
+import android.content.Intent
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
@@ -27,12 +28,16 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.tiles.DELAY_MS
+import com.android.systemui.qs.tiles.INTERVAL_MS
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-import com.android.systemui.recordissue.IssueRecordingService
+import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent
+import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
+import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.RecordingService
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
@@ -53,6 +58,7 @@
     private val panelInteractor: PanelInteractor,
     private val userContextProvider: UserContextProvider,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
+    private val recordingController: RecordingController,
 ) : QSTileUserActionInteractor<IssueRecordingModel> {
 
     override suspend fun handleInput(input: QSTileInput<IssueRecordingModel>) {
@@ -95,20 +101,22 @@
     }
 
     private fun startIssueRecordingService() =
-        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())
+        recordingController.startCountdown(
+            DELAY_MS,
+            INTERVAL_MS,
+            pendingServiceIntent(getStartIntent(userContextProvider.userContext)),
+            pendingServiceIntent(getStopIntent(userContextProvider.userContext))
+        )
 
     private fun stopIssueRecordingService() =
-        PendingIntent.getService(
-                userContextProvider.userContext,
-                RecordingService.REQUEST_CODE,
-                IssueRecordingService.getStopIntent(userContextProvider.userContext),
-                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
-            )
+        pendingServiceIntent(getStopIntent(userContextProvider.userContext))
             .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
+    private fun pendingServiceIntent(action: Intent) =
+        PendingIntent.getService(
+            userContextProvider.userContext,
+            RecordingService.REQUEST_CODE,
+            action,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index c2d112e..483373d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -20,13 +20,16 @@
 import android.content.Context
 import android.os.UserHandle
 import com.android.app.tracing.coroutines.flow.map
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.ModesTile
 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.modes.domain.model.ModesTileModel
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
+import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -61,28 +64,39 @@
     suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
 
     private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
-        val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes
-
         if (usesModeIcons()) {
-            val mainModeDrawable = activeModes.mainMode?.icon?.drawable
-            val iconResId = if (mainModeDrawable == null) modesIconResId else null
-
+            val tileIcon = getTileIcon(activeModes.mainMode)
             return ModesTileModel(
                 isActivated = activeModes.isAnyActive(),
-                icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(),
-                iconResId = iconResId,
+                icon = tileIcon.icon,
+                iconResId = tileIcon.resId,
                 activeModes = activeModes.modeNames
             )
         } else {
             return ModesTileModel(
                 isActivated = activeModes.isAnyActive(),
-                icon = context.getDrawable(modesIconResId)!!.asIcon(),
-                iconResId = modesIconResId,
+                icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(),
+                iconResId = ModesTile.ICON_RES_ID,
                 activeModes = activeModes.modeNames
             )
         }
     }
 
+    private data class TileIcon(val icon: Icon.Loaded, val resId: Int?)
+
+    private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon {
+        return if (activeMode != null) {
+            // ZenIconKey.resPackage is null if its resId is a system icon.
+            if (activeMode.icon.key.resPackage == null) {
+                TileIcon(activeMode.icon.drawable.asIcon(), activeMode.icon.key.resId)
+            } else {
+                TileIcon(activeMode.icon.drawable.asIcon(), null)
+            }
+        } else {
+            TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID)
+        }
+    }
+
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
 
     private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 7f571b1..69da313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -36,9 +36,7 @@
 ) : QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            if (!android.app.Flags.modesUiIcons()) {
-                iconRes = data.iconResId
-            }
+            iconRes = data.iconResId
             icon = { data.icon }
             activationState =
                 if (data.isActivated) {
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 e251c9e..98907b0 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
@@ -22,7 +22,9 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.AuthInteractionProperties
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -73,6 +75,8 @@
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.printSection
 import com.android.systemui.util.println
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
 import dagger.Lazy
 import java.io.PrintWriter
 import java.util.Optional
@@ -139,10 +143,13 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val vibratorHelper: VibratorHelper,
+    private val msdlPlayer: MSDLPlayer,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
 
+    private val authInteractionProperties = AuthInteractionProperties()
+
     override fun start() {
         if (SceneContainerFlag.isEnabled) {
             sceneLogger.logFrameworkEnabled(isEnabled = true)
@@ -541,9 +548,16 @@
                             deviceEntryHapticsInteractor.playSuccessHaptic
                                 .sample(sceneInteractor.currentScene)
                                 .collect { currentScene ->
-                                    vibratorHelper.vibrateAuthSuccess(
-                                        "$TAG, $currentScene device-entry::success"
-                                    )
+                                    if (Flags.msdlFeedback()) {
+                                        msdlPlayer.playToken(
+                                            MSDLToken.UNLOCK,
+                                            authInteractionProperties,
+                                        )
+                                    } else {
+                                        vibratorHelper.vibrateAuthSuccess(
+                                            "$TAG, $currentScene device-entry::success"
+                                        )
+                                    }
                                 }
                         }
 
@@ -551,9 +565,16 @@
                             deviceEntryHapticsInteractor.playErrorHaptic
                                 .sample(sceneInteractor.currentScene)
                                 .collect { currentScene ->
-                                    vibratorHelper.vibrateAuthError(
-                                        "$TAG, $currentScene device-entry::error"
-                                    )
+                                    if (Flags.msdlFeedback()) {
+                                        msdlPlayer.playToken(
+                                            MSDLToken.FAILURE,
+                                            authInteractionProperties,
+                                        )
+                                    } else {
+                                        vibratorHelper.vibrateAuthError(
+                                            "$TAG, $currentScene device-entry::error"
+                                        )
+                                    }
                                 }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 64dfc6c..735b4c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -54,6 +54,8 @@
             VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+    private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
 
     private final Executor mExecutor;
 
@@ -151,7 +153,7 @@
         vibrate(Process.myUid(),
                 "com.android.systemui",
                 BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason,
-                HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+                COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES);
     }
 
     /**
@@ -160,7 +162,7 @@
     public void vibrateAuthError(String reason) {
         vibrate(Process.myUid(), "com.android.systemui",
                 BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
-                HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+                COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index 2c462b7..77c4130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -198,12 +198,22 @@
                             parentView,
                             /* attachToRoot= */ false
                         ) as EnRouteView
-
                 InflatedContentViewHolder(newView) {
                     EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
                 }
             }
-            RichOngoingNotificationViewType.Expanded,
+            RichOngoingNotificationViewType.Expanded -> {
+                val newView =
+                    LayoutInflater.from(systemUiContext)
+                        .inflate(
+                            R.layout.notification_template_en_route_expanded,
+                            parentView,
+                            /* attachToRoot= */ false
+                        ) as EnRouteView
+                InflatedContentViewHolder(newView) {
+                    EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
+                }
+            }
             RichOngoingNotificationViewType.HeadsUp -> NullContentView
         }
     }
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 7c01784..8ff1ab6 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
@@ -3691,6 +3691,10 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_SCROLL: {
+                    // If scene container is active, NSSL should not control its own scrolling.
+                    if (SceneContainerFlag.isEnabled()) {
+                        return false;
+                    }
                     if (!mIsBeingDragged) {
                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                         if (vscroll != 0) {
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 43f9af6..dd4b000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -992,7 +992,7 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
             }
-            if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) {
+            if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing && isBouncerShowing()) {
                 hideAlternateBouncer(true);
                 mDismissCallbackRegistry.notifyDismissCancelled();
                 mPrimaryBouncerInteractor.setDismissAction(null, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index 85bbe7e..d601319 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -100,7 +100,14 @@
     val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
     val spn =
         if (statusBarSwitchToSpnFromDataSpn()) {
-            getStringExtra(EXTRA_SPN)
+            // Context: b/358669494. Use DATA_SPN if it exists, since that allows carriers to
+            // customize the display name. Otherwise, fall back to the SPN
+            val dataSpn = getStringExtra(EXTRA_DATA_SPN)
+            if (dataSpn.isNullOrEmpty()) {
+                getStringExtra(EXTRA_SPN)
+            } else {
+                dataSpn
+            }
         } else {
             getStringExtra(EXTRA_DATA_SPN)
         }
@@ -109,10 +116,8 @@
     val plmn = getStringExtra(EXTRA_PLMN)
 
     val str = StringBuilder()
-    val strData = StringBuilder()
     if (showPlmn && plmn != null) {
         str.append(plmn)
-        strData.append(plmn)
     }
     if (showSpn && spn != null) {
         if (str.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
index 4b0e5d1..6d99183 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -29,11 +29,12 @@
 class TelephonyInteractor
 @Inject
 constructor(
-    repository: TelephonyRepository,
+    private val repository: TelephonyRepository,
 ) {
     @Annotation.CallState val callState: Flow<Int> = repository.callState
 
     val isInCall: StateFlow<Boolean> = repository.isInCall
 
-    val hasTelephonyRadio: Boolean = repository.hasTelephonyRadio
+    val hasTelephonyRadio: Boolean
+        get() = repository.hasTelephonyRadio
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 8934d8f..d9e72bf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1305,13 +1305,12 @@
             if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
                         STREAM_UNKNOWN);
-                final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                 final int oldLevel = intent
                         .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
                 if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
-                        + " level=" + level + " oldLevel=" + oldLevel);
+                        + " oldLevel=" + oldLevel);
                 if (stream != STREAM_UNKNOWN) {
-                    changed = updateStreamLevelW(stream, level);
+                    changed |= onVolumeChangedW(stream, 0);
                 }
             } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index 43a78035..c42e25b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -29,7 +29,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -72,7 +72,7 @@
     val mainExecutor = FakeExecutor(fakeSystemClock)
     val backgroundExecutor = FakeExecutor(fakeSystemClock)
     private val kosmos = testKosmos()
-    private val msdlPlayer: FakeMSDLPlayer = kosmos.msdlPlayer
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
 
     lateinit var underTest: EmergencyButtonController
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index d3b7d22..662815e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -52,7 +52,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -92,6 +91,7 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    private static final int TEST_LAUNCH_SOURCE_ID = 1;
     private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF";
     private static final String DEVICE_NAME = "test_name";
     private static final String TEST_PKG = "pkg";
@@ -124,7 +124,7 @@
     @Mock
     private AudioManager mAudioManager;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private HearingDevicesUiEventLogger mUiEventLogger;
     @Mock
     private CachedBluetoothDevice mCachedDevice;
     @Mock
@@ -182,7 +182,8 @@
                 anyInt(), any());
         assertThat(intentCaptor.getValue().getAction()).isEqualTo(
                 Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
-        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
+        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR,
+                TEST_LAUNCH_SOURCE_ID);
     }
 
     @Test
@@ -196,7 +197,8 @@
                 anyInt(), any());
         assertThat(intentCaptor.getValue().getAction()).isEqualTo(
                 HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS);
-        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
+        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK,
+                TEST_LAUNCH_SOURCE_ID);
     }
 
     @Test
@@ -207,7 +209,8 @@
         mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
 
         verify(mCachedDevice).disconnect();
-        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
+        verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT,
+                TEST_LAUNCH_SOURCE_ID);
     }
 
     @Test
@@ -304,6 +307,7 @@
         mDialogDelegate = new HearingDevicesDialogDelegate(
                 mContext,
                 true,
+                TEST_LAUNCH_SOURCE_ID,
                 mDialogFactory,
                 mActivityStarter,
                 mDialogTransitionAnimator,
@@ -327,6 +331,7 @@
         mDialogDelegate = new HearingDevicesDialogDelegate(
                 mContext,
                 false,
+                TEST_LAUNCH_SOURCE_ID,
                 mDialogFactory,
                 mActivityStarter,
                 mDialogTransitionAnimator,
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 1e23690..7889b3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -61,10 +61,12 @@
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
 import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.events.ANIMATING_OUT
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -145,6 +147,9 @@
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
 
+    private val kosmos = testKosmos()
+    private val msdlPlayer = kosmos.msdlPlayer
+
     private var authContainer: TestAuthContainerView? = null
 
     @Before
@@ -668,7 +673,8 @@
             { credentialViewModel },
             fakeExecutor,
             vibrator,
-            lazyViewCapture
+            lazyViewCapture,
+            msdlPlayer,
         ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 4fc4166..55fd344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -37,12 +37,15 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.app.activityTaskManager
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.Utils.toBitmap
@@ -72,6 +75,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.util.mockito.withArgCaptor
+import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -124,6 +128,7 @@
     private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
     private val logoDescriptionFromApp = "Test Cake App"
     private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
+    private val authInteractionProperties = AuthInteractionProperties()
 
     /** Prompt panel size padding */
     private val smallHorizontalGuidelinePadding =
@@ -707,31 +712,66 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
         runGenericTest {
             val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
 
             kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
 
-            val confirmHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-            assertThat(confirmHaptics?.hapticFeedbackConstant)
-                .isEqualTo(
-                    if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
-                    else HapticFeedbackConstants.BIOMETRIC_CONFIRM
-                )
-            assertThat(confirmHaptics?.flag).isNull()
+            val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+            if (expectConfirmation) {
+                assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None)
+            } else {
+                val confirmHaptics =
+                    hapticsPreConfirm as PromptViewModel.HapticsToPlay.HapticConstant
+                assertThat(confirmHaptics.constant)
+                    .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+                assertThat(confirmHaptics.flag).isNull()
+            }
 
             if (expectConfirmation) {
                 kosmos.promptViewModel.confirmAuthenticated()
             }
 
-            val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-            assertThat(confirmedHaptics?.hapticFeedbackConstant)
+            val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+            val confirmedHaptics =
+                hapticsPostConfirm as PromptViewModel.HapticsToPlay.HapticConstant
+            assertThat(confirmedHaptics.constant)
                 .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
-            assertThat(confirmedHaptics?.flag).isNull()
+            assertThat(confirmedHaptics.flag).isNull()
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun set_msdl_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+        runGenericTest {
+            val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+            kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+            val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+
+            if (expectConfirmation) {
+                assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None)
+            } else {
+                val confirmHaptics = hapticsPreConfirm as PromptViewModel.HapticsToPlay.MSDL
+                assertThat(confirmHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+                assertThat(confirmHaptics.properties).isEqualTo(authInteractionProperties)
+            }
+
+            if (expectConfirmation) {
+                kosmos.promptViewModel.confirmAuthenticated()
+            }
+
+            val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+            val confirmedHaptics = hapticsPostConfirm as PromptViewModel.HapticsToPlay.MSDL
+            assertThat(confirmedHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+            assertThat(confirmedHaptics.properties).isEqualTo(authInteractionProperties)
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
         kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
@@ -740,20 +780,48 @@
             kosmos.promptViewModel.confirmAuthenticated()
         }
 
-        val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(currentHaptics?.hapticFeedbackConstant)
-            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
-        assertThat(currentHaptics?.flag).isNull()
+        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant
+        assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+        assertThat(currentHaptics.flag).isNull()
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playSuccessHaptic_SetsUnlockMSDLFeedback() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+        kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+        if (expectConfirmation) {
+            kosmos.promptViewModel.confirmAuthenticated()
+        }
+
+        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL
+        assertThat(currentHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+        assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun playErrorHaptic_SetsRejectConstant() = runGenericTest {
         kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
 
-        val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(currentHaptics?.hapticFeedbackConstant)
-            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
-        assertThat(currentHaptics?.flag).isNull()
+        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant
+        assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+        assertThat(currentHaptics.flag).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun playErrorHaptic_SetsFailureMSDLFeedback() = runGenericTest {
+        kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
+
+        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL
+        assertThat(currentHaptics.token).isEqualTo(MSDLToken.FAILURE)
+        assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties)
     }
 
     // biometricprompt_sfps_fingerprint_authenticating reused across rotations
@@ -855,6 +923,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun set_haptic_on_errors() = runGenericTest {
         kosmos.promptViewModel.showTemporaryError(
             "so sad",
@@ -863,13 +932,30 @@
             hapticFeedback = true,
         )
 
-        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(haptics?.hapticFeedbackConstant)
-            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
-        assertThat(haptics?.flag).isNull()
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant
+        assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+        assertThat(haptics.flag).isNull()
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun set_msdl_haptic_on_errors() = runGenericTest {
+        kosmos.promptViewModel.showTemporaryError(
+            "so sad",
+            messageAfterError = "",
+            authenticateAfterError = false,
+            hapticFeedback = true,
+        )
+
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL
+        assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE)
+        assertThat(haptics.properties).isEqualTo(authInteractionProperties)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
         kosmos.promptViewModel.showTemporaryError(
             "still sad",
@@ -878,11 +964,26 @@
             hapticFeedback = false,
         )
 
-        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None)
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun plays_msdl_haptic_on_errors_unless_skipped() = runGenericTest {
+        kosmos.promptViewModel.showTemporaryError(
+            "still sad",
+            messageAfterError = "",
+            authenticateAfterError = false,
+            hapticFeedback = false,
+        )
+
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
     fun plays_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
         kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -894,17 +995,39 @@
             hapticFeedback = true,
         )
 
-        val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant
         if (expectConfirmation) {
-            assertThat(haptics?.hapticFeedbackConstant)
-                .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
-            assertThat(haptics?.flag).isNull()
+            assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+            assertThat(haptics.flag).isNull()
         } else {
-            assertThat(haptics?.hapticFeedbackConstant)
-                .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+            assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
         }
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun plays_msdl_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+        kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+        kosmos.promptViewModel.showTemporaryError(
+            "still sad",
+            messageAfterError = "",
+            authenticateAfterError = false,
+            hapticFeedback = true,
+        )
+
+        val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+        val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL
+        if (expectConfirmation) {
+            assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE)
+        } else {
+            assertThat(haptics.token).isEqualTo(MSDLToken.UNLOCK)
+        }
+        assertThat(haptics.properties).isEqualTo(authInteractionProperties)
+    }
+
     private suspend fun TestScope.showTemporaryErrors(
         restart: Boolean,
         helpAfterError: String = "",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index a1bea06..6377717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -104,6 +104,7 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
+        when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
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 7cc9185..bfb8a57 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
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -86,6 +87,7 @@
                 { mock(DeviceEntryBackgroundViewModel::class.java) },
                 { falsingManager },
                 { mock(VibratorHelper::class.java) },
+                logcatLogBuffer(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
index 76c8cf0..7d41a20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -151,7 +153,7 @@
         mTile.handleClick(expandable);
         mTestableLooper.processAllMessages();
 
-        verify(mHearingDevicesDialogManager).showDialog(expandable);
+        verify(mHearingDevicesDialogManager).showDialog(expandable, LAUNCH_SOURCE_QS_TILE);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 73548ba..ca518f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.recordissue.TraceurMessageSender
 import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -65,6 +66,7 @@
     @Mock private lateinit var qsEventLogger: QsEventLogger
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var recordingController: RecordingController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@@ -109,6 +111,7 @@
                 Executors.newSingleThreadExecutor(),
                 issueRecordingState,
                 delegateFactory,
+                recordingController,
             )
     }
 
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 3e3c046..01a3d36 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
@@ -779,6 +779,26 @@
 
     @Test
     @DisableSceneContainer
+    public void testResetDoesNotHideBouncerWhenNotShowing() {
+        reset(mDismissCallbackRegistry);
+        reset(mPrimaryBouncerInteractor);
+
+        // GIVEN the keyguard is showing
+        reset(mAlternateBouncerInteractor);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+
+        // WHEN SBKV is reset with hideBouncerWhenShowing=true
+        mStatusBarKeyguardViewManager.reset(true);
+
+        // THEN no calls to hide should be made
+        verify(mAlternateBouncerInteractor, never()).hide();
+        verify(mDismissCallbackRegistry, never()).notifyDismissCancelled();
+        verify(mPrimaryBouncerInteractor, never()).setDismissAction(eq(null), eq(null));
+    }
+
+    @Test
+    @DisableSceneContainer
     public void testResetHideBouncerWhenShowing_alternateBouncerHides() {
         reset(mDismissCallbackRegistry);
         reset(mPrimaryBouncerInteractor);
@@ -786,6 +806,7 @@
         // GIVEN the keyguard is showing
         reset(mAlternateBouncerInteractor);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
 
         // WHEN SBKV is reset with hideBouncerWhenShowing=true
         mStatusBarKeyguardViewManager.reset(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index fe408e3..7634490 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -817,7 +817,7 @@
             captor.lastValue.onReceive(context, intent)
 
             // spnIntent() sets all values to true and test strings
-            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
 
             job.cancel()
         }
@@ -852,7 +852,7 @@
             verify(context).registerReceiver(captor.capture(), any())
             captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
 
             // WHEN an intent with a different subId is sent
             val wrongSubIntent = spnIntent(subId = 101)
@@ -860,7 +860,7 @@
             captor.lastValue.onReceive(context, wrongSubIntent)
 
             // THEN the previous intent's name is still used
-            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
 
             job.cancel()
         }
@@ -902,7 +902,7 @@
             verify(context).registerReceiver(captor.capture(), any())
             captor.lastValue.onReceive(context, intent)
 
-            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
 
             val intentWithoutInfo =
                 spnIntent(
@@ -961,7 +961,7 @@
 
             // The value is still there despite no active subscribers
             assertThat(underTest.networkName.value)
-                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+                .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
         }
 
     @Test
@@ -986,7 +986,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
-    fun networkName_allFieldsSet_doesNotUseDataSpn() =
+    fun networkName_allFieldsSet_prioritizesDataSpnOverSpn() =
         testScope.runTest {
             val latest by collectLastValue(underTest.networkName)
             val captor = argumentCaptor<BroadcastReceiver>()
@@ -1002,6 +1002,27 @@
                     plmn = PLMN,
                 )
             captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_spnAndPlmn_fallbackToSpnWhenNullDataSpn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = null,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
             assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
         }
 
@@ -1043,7 +1064,27 @@
                     plmn = PLMN,
                 )
             captor.lastValue.onReceive(context, intent)
-            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNotNull_showSpn_spnNotNull_dataSpnNull() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = null,
+                    showPlmn = true,
+                    plmn = PLMN,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
         }
 
     @Test
@@ -1102,10 +1143,50 @@
                     plmn = null,
                 )
             captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNull_showSpn_dataSpnNull() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = SPN,
+                    dataSpn = null,
+                    showPlmn = true,
+                    plmn = null,
+                )
+            captor.lastValue.onReceive(context, intent)
             assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN"))
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+    fun networkName_showPlmn_plmnNull_showSpn_bothSpnNull() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.networkName)
+            val captor = argumentCaptor<BroadcastReceiver>()
+            verify(context).registerReceiver(captor.capture(), any())
+            val intent =
+                spnIntent(
+                    subId = SUB_1_ID,
+                    showSpn = true,
+                    spn = null,
+                    dataSpn = null,
+                    showPlmn = true,
+                    plmn = null,
+                )
+            captor.lastValue.onReceive(context, intent)
+            assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+        }
+
+    @Test
     @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
     fun networkName_showPlmn_plmnNull_showSpn_flagOff() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 0358474..3041240 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -40,6 +41,7 @@
     @GuardedBy("mRegisteredReceivers")
     private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
     private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
+    private final Map<String, Context> mContextForPackage = new HashMap<>();
 
     public SysuiTestableContext(Context base) {
         super(base);
@@ -175,4 +177,22 @@
         }
         return super.createContextAsUser(user, flags);
     }
+
+    /**
+     * Sets a Context object that will be returned as the result of {@link #createPackageContext}
+     * for a specific {@code packageName}.
+     */
+    public void prepareCreatePackageContext(String packageName, Context context) {
+        mContextForPackage.put(packageName, context);
+    }
+
+    @Override
+    public Context createPackageContext(String packageName, int flags)
+            throws PackageManager.NameNotFoundException {
+        Context packageContext = mContextForPackage.get(packageName);
+        if (packageContext != null) {
+            return packageContext;
+        }
+        return super.createPackageContext(packageName, flags);
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
index 5ced578..3087d01 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.telephony.domain.interactor.telephonyInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -50,5 +51,6 @@
             },
         metricsLogger = metricsLogger,
         dozeLogger = mock(),
+        sceneInteractor = { sceneInteractor },
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
index f5a05b4..4f5c32a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -17,5 +17,7 @@
 package com.android.systemui.haptics.msdl
 
 import com.android.systemui.kosmos.Kosmos
+import com.google.android.msdl.domain.MSDLPlayer
 
-val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
+var Kosmos.msdlPlayer: MSDLPlayer by Kosmos.Fixture { fakeMSDLPlayer }
+val Kosmos.fakeMSDLPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
index 1e95fc1..740d891 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerWindowViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.mockito.mock
@@ -64,6 +65,7 @@
             },
             messageAreaViewModel = mock<AlternateBouncerMessageAreaViewModel>(),
             powerInteractor = powerInteractor,
+            touchLogBuffer = logcatLogBuffer(),
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 851a378..457bd28 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor
-import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
 import com.android.systemui.haptics.qs.qsLongPressEffect
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -155,5 +155,5 @@
     val scrimController by lazy { kosmos.scrimController }
     val scrimStartable by lazy { kosmos.scrimStartable }
     val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor }
-    val msdlPlayer by lazy { kosmos.msdlPlayer }
+    val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index b612a8b..9a5698c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
@@ -86,5 +87,6 @@
         statusBarStateController = sysuiStatusBarStateController,
         alternateBouncerInteractor = alternateBouncerInteractor,
         vibratorHelper = vibratorHelper,
+        msdlPlayer = msdlPlayer,
     )
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 9b0c8e5..333fe4c 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -127,6 +127,7 @@
     libs: [
         "framework-minus-apex.ravenwood",
         "ravenwood-junit",
+        "ravenwood-helper-libcore-runtime",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index f237ba9..1d182da 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -27,6 +27,9 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
 import org.junit.runner.Description;
 import org.junit.runner.Runner;
 import org.junit.runners.model.TestClass;
@@ -35,7 +38,7 @@
  * Provide hook points created by {@link RavenwoodAwareTestRunner}.
  */
 public class RavenwoodAwareTestRunnerHook {
-    private static final String TAG = "RavenwoodAwareTestRunnerHook";
+    private static final String TAG = RavenwoodAwareTestRunner.TAG;
 
     private RavenwoodAwareTestRunnerHook() {
     }
@@ -56,20 +59,36 @@
      * Called when a runner starts, before the inner runner gets a chance to run.
      */
     public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+        // TODO: Move the initialization code to a better place.
+
+        initOnce();
+
         // This log call also ensures the framework JNI is loaded.
         Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
                 + " runner=" + runner);
 
-        // TODO: Move the initialization code to a better place.
+        // This is needed to make AndroidJUnit4ClassRunner happy.
+        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+    }
+
+    private static boolean sInitialized = false;
+
+    private static void initOnce() {
+        if (sInitialized) {
+            return;
+        }
+        sInitialized = true;
+
+        // We haven't initialized liblog yet, so directly write to System.out here.
+        RavenwoodCommonUtils.log(TAG, "initOnce()");
+
+        // Redirect stdout/stdin to liblog.
+        RuntimeInit.redirectLogStreams();
 
         // This will let AndroidJUnit4 use the original runner.
         System.setProperty("android.junit.runner",
                 "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
         System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
-
-
-        // This is needed to make AndroidJUnit4ClassRunner happy.
-        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
     }
 
     /**
@@ -87,7 +106,7 @@
      */
     public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
             Scope scope, Order order) {
-        Log.i(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
+        Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
 
         if (scope == Scope.Class && order == Order.First) {
             // Keep track of the current class.
@@ -113,7 +132,7 @@
      */
     public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
             Scope scope, Order order, Throwable th) {
-        Log.i(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+        Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
 
         if (scope == Scope.Instance && order == Order.First) {
             getStats().onTestFinished(sCurrentClassDescription, description,
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 a2088fd..0059360 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -36,7 +36,6 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.internal.os.RuntimeInit;
 import com.android.server.LocalServices;
 
 import org.junit.runner.Description;
@@ -92,8 +91,6 @@
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
         }
 
-        RuntimeInit.redirectLogStreams();
-
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
         android.os.Binder.init$ravenwood();
         setSystemProperties(rule.mSystemProperties);
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 7d99166..bfde9cb 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -23,7 +23,6 @@
 
 import android.util.Log;
 
-import com.android.ravenwood.common.RavenwoodCommonUtils;
 import com.android.ravenwood.common.SneakyThrow;
 
 import org.junit.Assume;
@@ -75,7 +74,7 @@
  * (no hooks, etc.)
  */
 public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
-    private static final String TAG = "RavenwoodAwareTestRunner";
+    public static final String TAG = "Ravenwood";
 
     @Inherited
     @Target({TYPE})
@@ -142,16 +141,9 @@
     private Description mDescription = null;
     private Throwable mExceptionInConstructor = null;
 
-    /** Simple logging method. */
-    private void log(String message) {
-        RavenwoodCommonUtils.log(TAG, "[" + getTestClass().getJavaClass() + "  @" + this + "] "
-                + message);
-    }
-
-    private Error logAndFail(String message, Throwable innerException) {
-        log(message);
-        log("    Exception=" + innerException);
-        throw new AssertionError(message, innerException);
+    private Error logAndFail(String message, Throwable exception) {
+        Log.e(TAG, message, exception);
+        throw new AssertionError(message, exception);
     }
 
     public TestClass getTestClass() {
@@ -165,6 +157,8 @@
         try {
             mTestClass = new TestClass(testClass);
 
+            onRunnerInitializing();
+
             /*
              * If the class has @DisabledOnRavenwood, then we'll delegate to
              * ClassSkippingTestRunner, which simply skips it.
@@ -186,10 +180,8 @@
                 realRunnerClass = BlockJUnit4ClassRunner.class;
             }
 
-            onRunnerInitializing();
-
             try {
-                log("Initializing the inner runner: " + realRunnerClass);
+                Log.i(TAG, "Initializing the inner runner: " + realRunnerClass);
 
                 mRealRunner = instantiateRealRunner(realRunnerClass, testClass);
                 mDescription = mRealRunner.getDescription();
@@ -201,8 +193,7 @@
         } catch (Throwable th) {
             // If we throw in the constructor, Tradefed may not report it and just ignore the class,
             // so record it and throw it when the test actually started.
-            log("Fatal: Exception detected in constructor: " + th.getMessage() + "\n"
-                    + Log.getStackTraceString(th));
+            Log.e(TAG, "Fatal: Exception detected in constructor", th);
             mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
                     th);
             mDescription = Description.createTestDescription(testClass, "Constructor");
@@ -236,8 +227,7 @@
         if (!isOnRavenwood()) {
             return;
         }
-
-        log("onRunnerInitializing");
+        // DO NOT USE android.util.Log before calling onRunnerInitializing().
 
         RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass);
 
@@ -250,7 +240,7 @@
         if (!isOnRavenwood()) {
             return;
         }
-        log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+        Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
 
         for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
             ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
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 0f955e7..c519204 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
@@ -15,6 +15,9 @@
  */
 package com.android.platform.test.ravenwood.runtimehelper;
 
+import android.system.ErrnoException;
+import android.system.Os;
+
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 
 import java.io.File;
@@ -37,6 +40,14 @@
     private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv(
             "RAVENWOOD_SKIP_LOADING_LIBANDROID"));
 
+    /**
+     * If set to 1, and if $ANDROID_LOG_TAGS isn't set, we enable the verbose logging.
+     *
+     * (See also InitLogging() in http://ac/system/libbase/logging.cpp)
+     */
+    private static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv(
+            "RAVENWOOD_VERBOSE"));
+
     public static final String CORE_NATIVE_CLASSES = "core_native_classes";
     public static final String ICU_DATA_PATH = "icu.data.path";
     public static final String KEYBOARD_PATHS = "keyboard_paths";
@@ -123,6 +134,15 @@
             return;
         }
 
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            log("Force enabling verbose logging");
+            try {
+                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+            } catch (ErrnoException e) {
+                // Shouldn't happen.
+            }
+        }
+
         // Make sure these properties are not set.
         ensurePropertyNotSet(CORE_NATIVE_CLASSES);
         ensurePropertyNotSet(ICU_DATA_PATH);
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index a5c0b54..c94ef31 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -93,4 +93,8 @@
             throw new ErrnoException("pread", OsConstants.EIO, e);
         }
     }
+
+    public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
+        RavenwoodRuntimeNative.setenv(name, value, overwrite);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index 0d8408c..ad80d92 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -53,6 +53,9 @@
 
     private static native int nOpen(String path, int flags, int mode) throws ErrnoException;
 
+    public static native void setenv(String name, String value, boolean overwrite)
+            throws ErrnoException;
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index f5cb019f..c255be5 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -214,6 +214,19 @@
     return throwIfMinusOne(env, "open", TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)));
 }
 
+static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaValue,
+        jboolean overwrite) {
+    ScopedRealUtf8Chars name(env, javaName);
+    if (name.c_str() == NULL) {
+        jniThrowNullPointerException(env);
+    }
+    ScopedRealUtf8Chars value(env, javaValue);
+    if (value.c_str() == NULL) {
+        jniThrowNullPointerException(env);
+    }
+    throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
+}
+
 // ---- Registration ----
 
 static const JNINativeMethod sMethods[] =
@@ -227,6 +240,7 @@
     { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
     { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
+    { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index ffa4841..ee3bbca 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -216,6 +216,16 @@
 }
 
 flag {
+    name: "reset_input_dispatcher_before_first_touch_exploration"
+    namespace: "accessibility"
+    description: "Resets InputDispatcher state by sending ACTION_CANCEL before the first TouchExploration hover events"
+    bug: "364408887"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "scan_packages_without_lock"
     namespace: "accessibility"
     description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
@@ -228,6 +238,7 @@
     description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
     bug: "295575684"
 }
+
 flag {
     name: "send_hover_events_based_on_event_stream"
     namespace: "accessibility"
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 04b42e4..0ed239e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1613,6 +1613,19 @@
                 dispatchGesture(gestureEvent);
             }
             if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) {
+                if (Flags.resetInputDispatcherBeforeFirstTouchExploration()
+                        && !mState.hasResetInputDispatcherState()) {
+                    // Cancel any possible ongoing touch gesture from before touch exploration
+                    // started. This clears out the InputDispatcher event stream state so that it
+                    // is ready to accept new injected HOVER events.
+                    mDispatcher.sendMotionEvent(
+                            mEvents.get(0),
+                            ACTION_CANCEL,
+                            mRawEvents.get(0),
+                            mPointerIdBits,
+                            mPolicyFlags);
+                    setHasResetInputDispatcherState(true);
+                }
                 // Deliver a down event.
                 mDispatcher.sendMotionEvent(
                         mEvents.get(0),
@@ -1773,4 +1786,9 @@
                 + ", mDraggingPointerId: " + mDraggingPointerId
                 + " }";
     }
+
+    @VisibleForTesting
+    void setHasResetInputDispatcherState(boolean value) {
+        mState.setHasResetInputDispatcherState(value);
+    }
 }
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 e13994e..f15b8ee 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -86,6 +86,7 @@
     private MotionEvent mLastInjectedHoverEvent;
     // The last injected hover event used for performing clicks.
     private MotionEvent mLastInjectedHoverEventForClick;
+    private boolean mHasResetInputDispatcherState;
     // The time of the last injected down.
     private long mLastInjectedDownEventTime;
     // Keep track of which pointers sent to the system are down.
@@ -361,6 +362,14 @@
         return mLastInjectedDownEventTime;
     }
 
+    boolean hasResetInputDispatcherState() {
+        return mHasResetInputDispatcherState;
+    }
+
+    void setHasResetInputDispatcherState(boolean value) {
+        mHasResetInputDispatcherState = value;
+    }
+
     public int getLastTouchedWindowId() {
         return mLastTouchedWindowId;
     }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
similarity index 61%
rename from services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
rename to services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index e56f81f0..eba628d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -20,11 +20,16 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchManager.SearchContext;
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSession;
 import android.app.appsearch.GetSchemaResponse;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.SetSchemaResponse;
 import android.util.Slog;
@@ -33,22 +38,20 @@
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
- * Helper class for interacting with a system server local appsearch session asynchronously.
- *
- * <p>Converts the AppSearch Callback API to {@link AndroidFuture}.
+ * A future API wrapper of {@link AppSearchSession} APIs.
  */
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
-public class SyncAppSearchCallHelper implements Closeable {
-    private static final String TAG = SyncAppSearchCallHelper.class.getSimpleName();
+public class FutureAppSearchSession implements Closeable {
+    private static final String TAG = FutureAppSearchSession.class.getSimpleName();
     private final Executor mExecutor;
-    private final AppSearchManager mAppSearchManager;
     private final AndroidFuture<AppSearchResult<AppSearchSession>> mSettableSessionFuture;
 
-    public SyncAppSearchCallHelper(
+    public FutureAppSearchSession(
             @NonNull AppSearchManager appSearchManager,
             @NonNull Executor executor,
             @NonNull SearchContext appSearchContext) {
@@ -57,22 +60,21 @@
         Objects.requireNonNull(appSearchContext);
 
         mExecutor = executor;
-        mAppSearchManager = appSearchManager;
         mSettableSessionFuture = new AndroidFuture<>();
-        mAppSearchManager.createSearchSession(
+        appSearchManager.createSearchSession(
                 appSearchContext, mExecutor, mSettableSessionFuture::complete);
     }
 
     /** Converts a failed app search result codes into an exception. */
     @NonNull
-    private static Exception failedResultToException(@NonNull AppSearchResult appSearchResult) {
+    private static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) {
         return switch (appSearchResult.getResultCode()) {
-            case AppSearchResult.RESULT_INVALID_ARGUMENT ->
-                    new IllegalArgumentException(appSearchResult.getErrorMessage());
-            case AppSearchResult.RESULT_IO_ERROR ->
-                    new IOException(appSearchResult.getErrorMessage());
-            case AppSearchResult.RESULT_SECURITY_ERROR ->
-                    new SecurityException(appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+                    appSearchResult.getErrorMessage());
             default -> new IllegalStateException(appSearchResult.getErrorMessage());
         };
     }
@@ -91,7 +93,7 @@
     /** Gets the schema for a given app search session. */
     public AndroidFuture<GetSchemaResponse> getSchema() {
         return getSessionAsync()
-                .thenComposeAsync(
+                .thenCompose(
                         session -> {
                             AndroidFuture<AppSearchResult<GetSchemaResponse>>
                                     settableSchemaResponse = new AndroidFuture<>();
@@ -105,14 +107,13 @@
                                                     failedResultToException(result));
                                         }
                                     });
-                        },
-                        mExecutor);
+                        });
     }
 
     /** Sets the schema for a given app search session. */
     public AndroidFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest setSchemaRequest) {
         return getSessionAsync()
-                .thenComposeAsync(
+                .thenCompose(
                         session -> {
                             AndroidFuture<AppSearchResult<SetSchemaResponse>>
                                     settableSchemaResponse = new AndroidFuture<>();
@@ -130,8 +131,32 @@
                                                     failedResultToException(result));
                                         }
                                     });
-                        },
-                        mExecutor);
+                        });
+    }
+
+    /** Indexes documents into the AppSearchSession database. */
+    public AndroidFuture<AppSearchBatchResult<String, Void>> put(
+            @NonNull PutDocumentsRequest putDocumentsRequest) {
+        return getSessionAsync().thenCompose(
+                session -> {
+                    AndroidFuture<AppSearchBatchResult<String, Void>> batchResultFuture =
+                            new AndroidFuture<>();
+
+                    session.put(putDocumentsRequest, mExecutor, batchResultFuture::complete);
+                    return batchResultFuture;
+                });
+    }
+
+    /**
+     * Retrieves documents from the open AppSearchSession that match a given query string and type
+     * of search provided.
+     */
+    public AndroidFuture<FutureSearchResults> search(
+            @NonNull String queryExpression,
+            @NonNull SearchSpec searchSpec) {
+        return getSessionAsync().thenApply(
+                        session -> session.search(queryExpression, searchSpec))
+                .thenApply(result -> new FutureSearchResults(result, mExecutor));
     }
 
     @Override
@@ -142,4 +167,32 @@
             Slog.e(TAG, "Failed to close app search session", ex);
         }
     }
+
+    /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
+    public static class FutureSearchResults {
+        private final SearchResults mSearchResults;
+        private final Executor mExecutor;
+
+        public FutureSearchResults(@NonNull SearchResults searchResults,
+                @NonNull Executor executor) {
+            mSearchResults = Objects.requireNonNull(searchResults);
+            mExecutor = Objects.requireNonNull(executor);
+        }
+
+        public AndroidFuture<List<SearchResult>> getNextPage() {
+            AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
+                    new AndroidFuture<>();
+
+            mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
+            return nextPageFuture.thenApply(result -> {
+                if (result.isSuccess()) {
+                    return result.getResultValue();
+                } else {
+                    throw new RuntimeException(
+                            failedResultToException(result));
+                }
+            });
+        }
+
+    }
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3068398..b53bf98 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -220,6 +220,15 @@
     // See {@link Provider#pendingDeletedWidgetIds}.
     private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
 
+    // Hard limit of number of hosts an app can create, note that the app that hosts the widgets
+    // can have multiple instances of {@link AppWidgetHost}, typically in respect to different
+    // surfaces in the host app.
+    // @see AppWidgetHost
+    // @see AppWidgetHost#mHostId
+    private static final int MAX_NUMBER_OF_HOSTS_PER_PACKAGE = 20;
+    // Hard limit of number of widgets can be pinned by a host.
+    private static final int MAX_NUMBER_OF_WIDGETS_PER_HOST = 200;
+
     // Handles user and package related broadcasts.
     // See {@link #registerBroadcastReceiver}
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -2284,7 +2293,7 @@
         if (host != null) {
             return host;
         }
-
+        ensureHostCountBeforeAddLocked(id);
         host = new Host();
         host.id = id;
         mHosts.add(host);
@@ -2292,6 +2301,24 @@
         return host;
     }
 
+    /**
+     * Ensures that the number of hosts for a package is less than the maximum number of hosts per
+     * package. If the number of hosts is greater than the maximum number of hosts per package, then
+     * removes the oldest host.
+     */
+    private void ensureHostCountBeforeAddLocked(@NonNull final HostId hostId) {
+        final List<Host> hosts = new ArrayList<>();
+        for (Host host : mHosts) {
+            if (host.id.uid == hostId.uid
+                    && host.id.packageName.equals(hostId.packageName)) {
+                hosts.add(host);
+            }
+        }
+        while (hosts.size() >= MAX_NUMBER_OF_HOSTS_PER_PACKAGE) {
+            deleteHostLocked(hosts.remove(0));
+        }
+    }
+
     private void deleteHostLocked(Host host) {
         if (DEBUG) {
             Slog.i(TAG, "deleteHostLocked() " + host);
@@ -2377,6 +2404,11 @@
             }
 
             @Override
+            public void onNullBinding(ComponentName name) {
+                mContext.unbindService(this);
+            }
+
+            @Override
             public void onServiceDisconnected(ComponentName name) {
                 // Do nothing
             }
@@ -2524,6 +2556,11 @@
                             }
 
                             @Override
+                            public void onNullBinding(ComponentName name) {
+                                mContext.unbindService(this);
+                            }
+
+                            @Override
                             public void onServiceDisconnected(android.content.ComponentName name) {
                                 // Do nothing
                             }
@@ -3582,12 +3619,33 @@
         if (DEBUG) {
             Slog.i(TAG, "addWidgetLocked() " + widget);
         }
+        ensureWidgetCountBeforeAddLocked(widget);
         mWidgets.add(widget);
 
         onWidgetProviderAddedOrChangedLocked(widget);
     }
 
     /**
+     * Ensures that the widget count for the widget's host is not greater than the maximum
+     * number of widgets per host. If the count is greater than the maximum, removes oldest widgets
+     * from the host until the count is less than or equal to the maximum.
+     */
+    private void ensureWidgetCountBeforeAddLocked(@NonNull final Widget widget) {
+        if (widget.host == null || widget.host.id == null) {
+            return;
+        }
+        final List<Widget> widgetsInSameHost = new ArrayList<>();
+        for (Widget w : mWidgets) {
+            if (w.host != null && widget.host.id.equals(w.host.id)) {
+                widgetsInSameHost.add(w);
+            }
+        }
+        while (widgetsInSameHost.size() >= MAX_NUMBER_OF_WIDGETS_PER_HOST) {
+            removeWidgetLocked(widgetsInSameHost.remove(0));
+        }
+    }
+
+    /**
      * Checks if the provider is assigned and updates the mWidgetPackages to track packages
      * that have bound widgets.
      */
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d4f729c..3666524 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1516,9 +1516,8 @@
                 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;
+        final boolean wasStopped = hostApp == null ? r.appInfo.isStopped() : false;
+        final boolean firstLaunch = hostApp == null ? r.appInfo.isNotLaunched() : false;
 
         String error = bringUpServiceLocked(r, service.getFlags(), callerFg,
                 false /* whileRestarting */,
@@ -4308,9 +4307,8 @@
                         true, UNKNOWN_ADJ);
             }
 
-            final boolean wasStopped = hostApp == null ? wasStopped(s) : false;
-            final boolean firstLaunch =
-                    hostApp == null ? !mAm.wasPackageEverLaunched(s.packageName, s.userId) : false;
+            final boolean wasStopped = hostApp == null ? s.appInfo.isStopped() : false;
+            final boolean firstLaunch = hostApp == null ? s.appInfo.isNotLaunched() : false;
 
             boolean needOomAdj = false;
             if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
@@ -9350,8 +9348,4 @@
         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/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 1b00cec..6aadcdc 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.icu.text.SimpleDateFormat;
 import android.os.Binder;
+import android.os.Debug;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
@@ -495,6 +496,10 @@
 
     private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) {
         if (app == null) {
+            if (DEBUG) {
+                Slog.w(TAG,
+                        "app is null in addBaseFieldsFromProcessRecord: " + Debug.getCallers(4));
+            }
             return;
         }
         final int definingUid = app.getHostingRecord() != null
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8fe33d1..9e4666c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1005,13 +1005,10 @@
         final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
         final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
 
-        if ((info.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
-            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);
+        queue.setActiveWasStopped(info.isStopped());
+        queue.setActiveFirstLaunch(info.isNotLaunched());
 
+        final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
         final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
                 component, r.intent.getAction(), r.getHostingRecordTriggerType());
         final boolean isActivityCapable = (r.options != null
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 1314521..da40826 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -554,13 +554,11 @@
                                     callingProcessState, proc.mState.getCurProcState(),
                                     false, 0L);
                         } else {
-                            final boolean stopped =
-                                    (cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+                            final boolean stopped = cpr.appInfo.isStopped();
                             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);
+                            final boolean firstLaunch = cpr.appInfo.isNotLaunched();
                             checkTime(startTime, "getContentProviderImpl: before start process");
                             proc = mService.startProcessLocked(
                                     cpi.processName, cpr.appInfo, false, 0,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index bb0c24b..5b4e573 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3394,24 +3394,33 @@
                 hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
         final ProcessStateRecord state = r.mState;
 
-        final boolean wasStopped = (info.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+        final boolean wasStopped = info.isStopped();
         // Check if we should mark the processrecord for first launch after force-stopping
         if (wasStopped) {
+            boolean wasEverLaunched;
+            if (android.app.Flags.useAppInfoNotLaunched()) {
+                wasEverLaunched = !info.isNotLaunched();
+            } else {
+                wasEverLaunched = mService.getPackageManagerInternal()
+                        .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+            }
             // 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
                 // should it be considered as force-stopped.
-                @WindowProcessController.StoppedState int stoppedState = wasPackageEverLaunched
+                @WindowProcessController.StoppedState int stoppedState = wasEverLaunched
                         ? 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
+                if (android.app.Flags.useAppInfoNotLaunched()) {
+                    // If it was launched before, then it must be a force-stop
+                    r.setWasForceStopped(wasEverLaunched);
+                } else {
+                    r.setWasForceStopped(true);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4e24cf3..780eda6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -285,7 +285,6 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -411,6 +410,9 @@
     /** The controller for the volume UI. */
     private final VolumeController mVolumeController = new VolumeController();
 
+    /** Used only for testing to enable/disable the long press timeout volume actions. */
+    private final AtomicBoolean mVolumeControllerLongPressEnabled = new AtomicBoolean(true);
+
     // sendMsg() flags
     /** If the msg is already queued, replace it with this one. */
     private static final int SENDMSG_REPLACE = 0;
@@ -12554,6 +12556,15 @@
         if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible);
     }
 
+    /** @see AudioManager#setVolumeControllerLongPressTimeoutEnabled(boolean) */
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) {
+        super.setVolumeControllerLongPressTimeoutEnabled_enforcePermission();
+        mVolumeControllerLongPressEnabled.set(enable);
+        Log.i(TAG, "Volume controller long press timeout enabled: " + enable);
+    }
+
     @Override
     public void setVolumePolicy(VolumePolicy policy) {
         enforceVolumeController("set volume policy");
@@ -12632,7 +12643,9 @@
                 if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
                     // UI is not visible yet, adjustment is ignored
                     if (mNextLongPress < now) {
-                        mNextLongPress = now + mLongPressTimeout;
+                        mNextLongPress =
+                                now + (mVolumeControllerLongPressEnabled.get() ? mLongPressTimeout
+                                        : 0);
                     }
                     suppress = true;
                 } else if (mNextLongPress > 0) {  // in a long-press
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 907e7c6..86015ac 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -938,7 +938,7 @@
             setAmbientLux(mFastAmbientLux);
             if (mLoggingEnabled) {
                 Slog.d(TAG, "updateAmbientLux: "
-                        + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+                        + ((mFastAmbientLux > mPreThresholdLux) ? "Brightened" : "Darkened") + ": "
                         + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
                         + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 334dda0..01bbd2f 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.hardware.display.BrightnessInfo;
+import android.os.PowerManager;
 import android.text.TextUtils;
 
 import com.android.server.display.brightness.BrightnessEvent;
@@ -255,7 +256,7 @@
         private String mDisplayBrightnessStrategyName;
         private boolean mShouldUseAutoBrightness;
         private boolean mIsSlowChange;
-        private float mMaxBrightness;
+        private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
         private float mMinBrightness;
         private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
         private boolean mShouldUpdateScreenBrightnessSetting;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2b732ea..ed16b14 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -134,6 +134,7 @@
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -1275,6 +1276,9 @@
                         || isUidPresentOnDisplayInternal(callingUid, displayId)) {
                     return info;
                 }
+            } else if (displayId == Display.DEFAULT_DISPLAY) {
+                Slog.e(TAG, "Default display is null for info request from uid "
+                        + callingUid);
             }
             return null;
         }
@@ -2223,10 +2227,11 @@
             if (display.isValidLocked()) {
                 applyDisplayChangedLocked(display);
             }
-            return;
+        } else {
+            releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         }
 
-        releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+        Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked());
     }
 
     private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) {
@@ -3098,6 +3103,7 @@
 
     /**
      * Get internal or external viewport. Create it if does not currently exist.
+     *
      * @param viewportType - either INTERNAL or EXTERNAL
      * @return the viewport with the requested type
      */
@@ -4413,7 +4419,6 @@
         }
 
 
-
         @Override // Binder call
         public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) {
             final String uniqueId;
@@ -4492,10 +4497,12 @@
         @Override // Binder call
         public void setBrightness(int displayId, float brightness) {
             setBrightness_enforcePermission();
-            if (!isValidBrightness(brightness)) {
-                Slog.w(TAG, "Attempted to set invalid brightness" + brightness);
+            if (Float.isNaN(brightness)) {
+                Slog.w(TAG, "Attempted to set invalid brightness: " + brightness);
                 return;
             }
+            MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_MIN,
+                    PowerManager.BRIGHTNESS_MAX);
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -4791,12 +4798,6 @@
         }
     }
 
-    private static boolean isValidBrightness(float brightness) {
-        return !Float.isNaN(brightness)
-                && (brightness >= PowerManager.BRIGHTNESS_MIN)
-                && (brightness <= PowerManager.BRIGHTNESS_MAX);
-    }
-
     @VisibleForTesting
     void overrideSensorManager(SensorManager sensorManager) {
         synchronized (mSyncRoot) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bb2bed7..7c591e3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2222,6 +2222,7 @@
                 unblockScreenOn();
             }
             mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
+            Slog.i(TAG, "Window Manager Policy screenTurningOn complete");
         }
 
         // Return true if the screen isn't blocked.
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 40e9198..cf44ac0 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -73,7 +73,6 @@
     abstract void stop();
 
     protected enum Type {
-        THERMAL,
         POWER,
         WEAR_BEDTIME_MODE,
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index d3be33f..9404034 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -72,6 +72,8 @@
     private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>();
     private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>();
     private final List<UserSwitchListener> mUserSwitchListeners = new ArrayList<>();
+    private final List<DeviceConfigListener> mDeviceConfigListeners = new ArrayList<>();
+
     private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
 
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
@@ -144,9 +146,14 @@
             if (m instanceof UserSwitchListener l) {
                 mUserSwitchListeners.add(l);
             }
+            if (m instanceof DeviceConfigListener l) {
+                mDeviceConfigListeners.add(l);
+            }
         });
-        mOnPropertiesChangedListener =
-                properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+        mOnPropertiesChangedListener = properties -> {
+            mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+            mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged);
+        };
         mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
         start();
     }
@@ -209,8 +216,6 @@
     private int getBrightnessMaxReason() {
         if (mClamperType == null) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
-        } else if (mClamperType == Type.THERMAL) {
-            return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
         } else if (mClamperType == Type.POWER) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
         } else if (mClamperType == Type.WEAR_BEDTIME_MODE) {
@@ -225,7 +230,7 @@
      * Called when the user switches.
      */
     public void onUserSwitch() {
-        mUserSwitchListeners.forEach(listener -> listener.onSwitchUser());
+        mUserSwitchListeners.forEach(UserSwitchListener::onSwitchUser);
     }
 
     /**
@@ -294,11 +299,14 @@
                 state2.mMaxDesiredHdrRatio)
                 || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness,
                 state2.mMaxHdrBrightness)
-                || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline;
+                || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline
+                || state1.mMaxBrightnessReason != state2.mMaxBrightnessReason
+                || !BrightnessSynchronizer.floatEquals(state1.mMaxBrightness,
+                state2.mMaxBrightness);
     }
 
     private void start() {
-        if (!mClampers.isEmpty()) {
+        if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) {
             mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
                     mExecutor, mOnPropertiesChangedListener);
         }
@@ -333,8 +341,7 @@
                 ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
                 DisplayManagerFlags flags, Context context, float currentBrightness) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
-            clampers.add(
-                    new BrightnessThermalClamper(handler, clamperChangeListener, data));
+
             if (flags.isPowerThrottlingClamperEnabled()) {
                 // Check if power-throttling config is present.
                 PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
@@ -354,6 +361,8 @@
                 Handler handler, ClamperChangeListener listener,
                 DisplayDeviceData data) {
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
+            modifiers.add(new BrightnessThermalModifier(handler, listener, data));
+
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
             if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
@@ -384,7 +393,7 @@
     /**
      * Config Data for clampers/modifiers
      */
-    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
+    public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData,
             BrightnessPowerClamper.PowerData,
             BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
         @NonNull
@@ -498,13 +507,23 @@
     }
 
     /**
+     * Modifier should implement this interface in order to receive device config updates
+     */
+    interface DeviceConfigListener {
+        void onDeviceConfigChanged();
+    }
+
+    /**
      * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness
-     * adjustement is needed
+     * adjustment is needed
      */
     public static class ModifiersAggregatedState {
         float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO;
         float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX;
         @Nullable
         Spline mSdrHdrRatioSpline = null;
+        @BrightnessInfo.BrightnessMaxReason
+        int mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+        float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java
similarity index 77%
rename from services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
rename to services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java
index 4498258..21ef309 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java
@@ -22,6 +22,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
@@ -33,8 +35,10 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.DeviceConfigParsingUtils;
@@ -43,12 +47,15 @@
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
 
-class BrightnessThermalClamper extends
-        BrightnessClamper<BrightnessThermalClamper.ThermalData> {
+class BrightnessThermalModifier implements BrightnessStateModifier,
+        BrightnessClamperController.DisplayDeviceDataListener,
+        BrightnessClamperController.StatefulModifier,
+        BrightnessClamperController.DeviceConfigListener {
 
     private static final String TAG = "BrightnessThermalClamper";
     @NonNull
@@ -58,6 +65,11 @@
     // data from DeviceConfig, for all displays, for all dataSets
     // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
     @NonNull
+    protected final Handler mHandler;
+    @NonNull
+    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
+
+    @NonNull
     private Map<String, Map<String, ThermalBrightnessThrottlingData>>
             mThermalThrottlingDataOverride = Map.of();
     // data from DisplayDeviceConfig, for particular display+dataSet
@@ -73,6 +85,8 @@
     private String mDataId = null;
     @Temperature.ThrottlingStatus
     private int mThrottlingStatus = Temperature.THROTTLING_NONE;
+    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+    private boolean mApplied = false;
 
     private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
         try {
@@ -88,53 +102,51 @@
             mDataSetMapper = ThermalBrightnessThrottlingData::create;
 
 
-    BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
-            ThermalData thermalData) {
-        this(new Injector(), handler, listener, thermalData);
+    BrightnessThermalModifier(Handler handler, ClamperChangeListener listener,
+            BrightnessClamperController.DisplayDeviceData data) {
+        this(new Injector(), handler, listener, data);
     }
 
     @VisibleForTesting
-    BrightnessThermalClamper(Injector injector, Handler handler,
-            ClamperChangeListener listener, ThermalData thermalData) {
-        super(handler, listener);
+    BrightnessThermalModifier(Injector injector, @NonNull Handler handler,
+            @NonNull ClamperChangeListener listener,
+            @NonNull BrightnessClamperController.DisplayDeviceData data) {
+        mHandler = handler;
+        mChangeListener = listener;
         mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mThermalStatusObserver = new ThermalStatusObserver(injector, handler);
         mHandler.post(() -> {
-            setDisplayData(thermalData);
-            loadOverrideData();
-        });
-
-    }
-
-    @Override
-    @NonNull
-    Type getType() {
-        return Type.THERMAL;
-    }
-
-    @Override
-    void onDeviceConfigChanged() {
-        mHandler.post(() -> {
-            loadOverrideData();
-            recalculateActiveData();
-        });
-    }
-
-    @Override
-    void onDisplayChanged(ThermalData data) {
-        mHandler.post(() -> {
             setDisplayData(data);
-            recalculateActiveData();
+            loadOverrideData();
         });
     }
+    //region BrightnessStateModifier
+    @Override
+    public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        if (stateBuilder.getMaxBrightness() > mBrightnessCap) {
+            stateBuilder.setMaxBrightness(mBrightnessCap);
+            stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap));
+            stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
+            stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            // set fast change only when modifier is activated.
+            // this will allow auto brightness to apply slow change even when modifier is active
+            if (!mApplied) {
+                stateBuilder.setIsSlowChange(false);
+            }
+            mApplied = true;
+        } else {
+            mApplied = false;
+        }
+    }
 
     @Override
-    void stop() {
+    public void stop() {
         mThermalStatusObserver.stopObserving();
     }
 
     @Override
-    void dump(PrintWriter writer) {
+    public void dump(PrintWriter writer) {
         writer.println("BrightnessThermalClamper:");
         writer.println("  mThrottlingStatus: " + mThrottlingStatus);
         writer.println("  mUniqueDisplayId: " + mUniqueDisplayId);
@@ -142,10 +154,53 @@
         writer.println("  mDataOverride: " + mThermalThrottlingDataOverride);
         writer.println("  mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
         writer.println("  mDataActive: " + mThermalThrottlingDataActive);
+        writer.println("  mBrightnessCap:" + mBrightnessCap);
+        writer.println("  mApplied:" + mApplied);
         mThermalStatusObserver.dump(writer);
-        super.dump(writer);
     }
 
+    @Override
+    public boolean shouldListenToLightSensor() {
+        return false;
+    }
+
+    @Override
+    public void setAmbientLux(float lux) {
+        // noop
+    }
+    //endregion
+
+    //region DisplayDeviceDataListener
+    @Override
+    public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+    //endregion
+
+    //region StatefulModifier
+    @Override
+    public void applyStateChange(
+            BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+        if (aggregatedState.mMaxBrightness > mBrightnessCap) {
+            aggregatedState.mMaxBrightness = mBrightnessCap;
+            aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+        }
+    }
+    //endregion
+
+    //region DeviceConfigListener
+    @Override
+    public void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+    //endregion
+
     private void recalculateActiveData() {
         if (mUniqueDisplayId == null || mDataId == null) {
             return;
@@ -176,14 +231,11 @@
 
     private void recalculateBrightnessCap() {
         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
-        boolean isActive = false;
-
         if (mThermalThrottlingDataActive != null) {
             // Throttling levels are sorted by increasing severity
             for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
                 if (level.thermalStatus <= mThrottlingStatus) {
                     brightnessCap = level.brightness;
-                    isActive = true;
                 } else {
                     // Throttling levels that are greater than the current status are irrelevant
                     break;
@@ -191,9 +243,8 @@
             }
         }
 
-        if (brightnessCap  != mBrightnessCap || mIsActive != isActive) {
+        if (brightnessCap  != mBrightnessCap) {
             mBrightnessCap = brightnessCap;
-            mIsActive = isActive;
             mChangeListener.onChanged();
         }
     }
@@ -205,7 +256,6 @@
         }
     }
 
-
     private final class ThermalStatusObserver extends IThermalEventListener.Stub {
         private final Injector mInjector;
         private final Handler mHandler;
@@ -228,7 +278,7 @@
 
             String curType = mObserverTempSensor.type;
             mObserverTempSensor = tempSensor;
-            if (curType.equals(tempSensor.type)) {
+            if (Objects.equals(curType, tempSensor.type)) {
                 Slog.d(TAG, "Thermal status observer already started");
                 return;
             }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 234d6d3..236333e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -53,6 +53,10 @@
     // Default state used in common by all the feature actions.
     protected static final int STATE_NONE = 0;
 
+    // Delay to query avr's audio status, some avrs could report the old volume first. It could
+    // show inverse mute state on TV.
+    protected static final long DELAY_GIVE_AUDIO_STATUS = 500;
+
     // Internal state indicating the progress of action.
     protected int mState = STATE_NONE;
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3c7b9d3..271836a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1638,6 +1638,10 @@
         mHandler.post(new WorkSourceUidPreservingRunnable(runnable));
     }
 
+    void runOnServiceThreadDelayed(Runnable runnable, long delay) {
+        mHandler.postDelayed(new WorkSourceUidPreservingRunnable(runnable), delay);
+    }
+
     private void assertRunOnServiceThread() {
         if (Looper.myLooper() != mHandler.getLooper()) {
             throw new IllegalStateException("Should run on service thread.");
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 7e18d84..11682f6 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -180,15 +180,22 @@
                 && localDevice().getService().isAbsoluteVolumeBehaviorEnabled()) {
             sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
                     mTargetAddress),
-                    __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
-                            getSourceAddress(),
-                            localDevice().findAudioReceiverAddress())));
+                    __ -> queryAvrAudioStatus());
         } else {
             sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
                     mTargetAddress));
         }
     }
 
+    private void queryAvrAudioStatus() {
+        localDevice().mService.runOnServiceThreadDelayed(
+                () -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        getSourceAddress(),
+                        localDevice().findAudioReceiverAddress())),
+                DELAY_GIVE_AUDIO_STATUS);
+
+    }
+
     @Override
     public boolean processCommand(HdmiCecMessage cmd) {
         // Send key action doesn't need any incoming CEC command, hence does not consume it.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index af0ccf9..f747879 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4678,6 +4678,7 @@
         }
     }
 
+    // TODO(b/356239178): Make dump proto multi-user aware.
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
             final int userId = mCurrentImeUserId;
@@ -6103,17 +6104,40 @@
     @BinderThread
     private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
             boolean isCritical) {
+        final int argUserId = parseUserIdFromDumpArgs(args);
+        final Printer p = new PrintWriterPrinter(pw);
+        p.println("Current Input Method Manager state:");
+        p.println("  concurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
+        if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) {
+            mUserDataRepository.forAllUserData(
+                    u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical));
+        } else {
+            final int userId = argUserId != UserHandle.USER_NULL ? argUserId : mCurrentImeUserId;
+            final var userData = getUserData(userId);
+            dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
+        }
+    }
+
+    @UserIdInt
+    private static int parseUserIdFromDumpArgs(String[] args) {
+        final int userIdx = Arrays.binarySearch(args, "--user");
+        if (userIdx == -1 || userIdx == args.length - 1) {
+            return UserHandle.USER_NULL;
+        }
+        return Integer.parseInt(args[userIdx + 1]);
+    }
+
+    // TODO(b/356239178): Update dump format output to better group per-user info.
+    @BinderThread
+    private void dumpAsStringNoCheckForUser(UserData userData, FileDescriptor fd, PrintWriter pw,
+            String[] args, boolean isCritical) {
+        final Printer p = new PrintWriterPrinter(pw);
         IInputMethodInvoker method;
         ClientState client;
-
-        final Printer p = new PrintWriterPrinter(pw);
-
+        p.println("  UserId=" + userData.mUserId);
         synchronized (ImfLock.class) {
-            final int userId = mCurrentImeUserId;
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-            final var userData = getUserData(userId);
-            p.println("Current Input Method Manager state:");
-            p.println("  concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(
+                    userData.mUserId);
             final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
             p.println("  Input Methods:");
@@ -6133,7 +6157,7 @@
                 p.println("    sessionRequested="
                         + c.mSessionRequested);
                 p.println("    sessionRequestedForAccessibility="
-                                + c.mSessionRequestedForAccessibility);
+                        + c.mSessionRequestedForAccessibility);
                 p.println("    curSession=" + c.mCurSession);
                 p.println("    selfReportedDisplayId=" + c.mSelfReportedDisplayId);
                 p.println("    uid=" + c.mUid);
@@ -6141,7 +6165,6 @@
             };
             mClientController.forAllClients(clientControllerDump);
             final var bindingController = userData.mBindingController;
-            p.println("  mCurrentImeUserId=" + userData.mUserId);
             p.println("  mCurMethodId=" + bindingController.getSelectedMethodId());
             client = userData.mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq="
@@ -6234,8 +6257,6 @@
             p.println("No input method client.");
         }
         synchronized (ImfLock.class) {
-            final int userId = mCurrentImeUserId;
-            final var userData = getUserData(userId);
             if (userData.mImeBindingState.mFocusedWindowClient != null
                     && client != userData.mImeBindingState.mFocusedWindowClient) {
                 p.println(" ");
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0cc50e6..3523a33 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -103,7 +103,7 @@
  *  - A remote interface definition (aidl) provided by the service used for communication.
  */
 abstract public class ManagedServices {
-    protected final String TAG = getClass().getSimpleName();
+    protected final String TAG = getClass().getSimpleName().replace('$', '.');
     protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 1c70af0..5ca5fda 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -397,6 +397,8 @@
         ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
                 | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
                 | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
+        ai.privateFlagsExt |=
+                flag(state.isNotLaunched(), ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED);
         if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
             ai.enabled = true;
         } else if (state.getEnabledState()
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 749025b..1fcd7f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -810,7 +810,7 @@
                     event.recycle();
                     break;
                 case MSG_HANDLE_ALL_APPS:
-                    launchAllAppsAction();
+                    launchAllAppsAction((KeyEvent) msg.obj);
                     break;
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
@@ -1879,26 +1879,31 @@
         }
     }
 
-    private void launchAllAppsAction() {
-        Intent intent = new Intent(Intent.ACTION_ALL_APPS);
-        if (mHasFeatureLeanback) {
-            Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
-            intentLauncher.addCategory(Intent.CATEGORY_HOME);
-            ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher,
-                    PackageManager.MATCH_SYSTEM_ONLY,
-                    mCurrentUserId);
-            if (resolveInfo != null) {
-                intent.setPackage(resolveInfo.activityInfo.packageName);
+    private void launchAllAppsAction(KeyEvent event) {
+        if (mHasFeatureLeanback || mHasFeatureWatch) {
+            // TV and watch support the all apps intent
+            notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
+            Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+            if (mHasFeatureLeanback) {
+                Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
+                intentLauncher.addCategory(Intent.CATEGORY_HOME);
+                ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher,
+                        PackageManager.MATCH_SYSTEM_ONLY,
+                        mCurrentUserId);
+                if (resolveInfo != null) {
+                    intent.setPackage(resolveInfo.activityInfo.packageName);
+                }
             }
-        }
-        startActivityAsUser(intent, UserHandle.CURRENT);
-    }
-
-    private void launchAllAppsViaA11y() {
-        AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal();
-        if (accessibilityManager != null) {
-            accessibilityManager.performSystemAction(
-                    AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+            startActivityAsUser(intent, UserHandle.CURRENT);
+        } else {
+            notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
+            AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal();
+            if (accessibilityManager != null) {
+                accessibilityManager.performSystemAction(
+                        AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+            }
         }
         dismissKeyboardShortcutsMenu();
     }
@@ -2076,15 +2081,7 @@
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press");
             switch (mLongPressOnHomeBehavior) {
                 case LONG_PRESS_HOME_ALL_APPS:
-                    if (mHasFeatureLeanback) {
-                        launchAllAppsAction();
-                        notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
-                    } else {
-                        launchAllAppsViaA11y();
-                        notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
-                    }
+                    launchAllAppsAction(event);
                     break;
                 case LONG_PRESS_HOME_ASSIST:
                     notifyKeyGestureCompleted(event,
@@ -3695,18 +3692,11 @@
                 break;
             case KeyEvent.KEYCODE_ALL_APPS:
                 if (firstDown) {
-                    if (mHasFeatureLeanback) {
-                        mHandler.removeMessages(MSG_HANDLE_ALL_APPS);
-                        Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
-                        msg.setAsynchronous(true);
-                        msg.sendToTarget();
-                        notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
-                    } else {
-                        launchAllAppsViaA11y();
-                        notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
-                    }
+                    mHandler.removeMessages(MSG_HANDLE_ALL_APPS);
+
+                    Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS, new KeyEvent(event));
+                    msg.setAsynchronous(true);
+                    msg.sendToTarget();
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
@@ -3759,9 +3749,7 @@
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
-                            launchAllAppsViaA11y();
-                            notifyKeyGestureCompleted(event,
-                                    KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
+                            launchAllAppsAction(event);
                         }
                         mPendingMetaAction = false;
                     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 12e7fd0..71cb882 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3668,7 +3668,7 @@
                         mBrightWhenDozingConfig);
                 int wakefulness = powerGroup.getWakefulnessLocked();
                 if (DEBUG_SPEW) {
-                    Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
+                    Slog.d(TAG, "updatePowerGroupsLocked: displayReady=" + ready
                             + ", groupId=" + groupId
                             + ", policy=" + policyToString(powerGroup.getPolicyLocked())
                             + ", mWakefulness="
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 60b5e65..91a17a9 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -599,13 +599,6 @@
             ComponentName component = it.next();
             ServiceState serviceState = userState.serviceStateMap.get(component);
             if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
-                if (serviceState.callback != null) {
-                    try {
-                        serviceState.service.unregisterCallback(serviceState.callback);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error in unregisterCallback", e);
-                    }
-                }
                 unbindService(serviceState);
                 it.remove();
             }
@@ -667,13 +660,6 @@
             // Unregister all callbacks and unbind all services.
             for (ServiceState serviceState : userState.serviceStateMap.values()) {
                 if (serviceState.service != null) {
-                    if (serviceState.callback != null) {
-                        try {
-                            serviceState.service.unregisterCallback(serviceState.callback);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in unregisterCallback", e);
-                        }
-                    }
                     unbindService(serviceState);
                 }
             }
@@ -3571,12 +3557,19 @@
 
     @GuardedBy("mLock")
     private void unbindService(ServiceState serviceState) {
-        if (!serviceState.bound) {
+        if (serviceState == null || !serviceState.bound) {
             return;
         }
         if (DEBUG) {
             Slog.d(TAG, "unbindService(service=" + serviceState.component + ")");
         }
+        if (serviceState.callback != null) {
+            try {
+                serviceState.service.unregisterCallback(serviceState.callback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in unregisterCallback", e);
+            }
+        }
         mContext.unbindService(serviceState.connection);
         serviceState.bound = false;
         serviceState.service = null;
@@ -3794,9 +3787,9 @@
                     if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
                         return;
                     }
-                    Slog.d("ServiceCallback",
-                            "addHardwareInput: device id " + deviceId + ", "
-                                    + inputInfo.toString());
+                    Slog.d(TAG, "ServiceCallback: addHardwareInput, deviceId: " + deviceId +
+                                ", inputInfo: " + inputInfo.toString() + " by " + mComponent +
+                                ", userId: " + mUserId);
                     mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
                     addHardwareInputLocked(inputInfo, mComponent, mUserId);
                 }
@@ -3815,6 +3808,9 @@
                     if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
                         return;
                     }
+                    Slog.d(TAG, "ServiceCallback: addHdmiInput, id: " + id +
+                                ", inputInfo: "+ inputInfo.toString() + " by " + mComponent +
+                                ", userId: " + mUserId);
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                     addHardwareInputLocked(inputInfo, mComponent, mUserId);
                     if (mOnScreenInputId != null && mOnScreenSessionState != null) {
@@ -3845,8 +3841,8 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    Slog.d("ServiceCallback",
-                            "removeHardwareInput " + inputId + " by " + mComponent);
+                    Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId +
+                                " by " + mComponent + ", userId: " + mUserId);
                     removeHardwareInputLocked(inputId, mUserId);
                 }
             } finally {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index ecd140e..96a25da 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -44,7 +44,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.telephony.flags.Flags;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
@@ -324,11 +323,8 @@
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             // Get only configs as needed to save memory.
             final PersistableBundle carrierConfig =
-                    Flags.fixCrashOnGettingConfigWhenPhoneIsGone()
-                            ? CarrierConfigManager.getCarrierConfigSubset(mContext, subId,
-                                    VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS)
-                            : mCarrierConfigManager.getConfigForSubId(subId,
-                                    VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+                    CarrierConfigManager.getCarrierConfigSubset(mContext, subId,
+                            VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
             if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
                 mReadySubIdsBySlotId.put(slotId, subId);
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 85e24c1..b6e45fc8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -14799,7 +14799,8 @@
     }
 
     @Override
-    public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) {
+    public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled,
+            PersistableBundle options) {
         Objects.requireNonNull(who, "ComponentName is null");
 
         // Check can set secondary lockscreen enabled
@@ -18644,11 +18645,9 @@
 
         toggleBackupServiceActive(caller.getUserId(), enabled);
 
-        if (Flags.backupServiceSecurityLogEventEnabled()) {
-            if (SecurityLog.isLoggingEnabled()) {
-                SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
-                        caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
-            }
+        if (SecurityLog.isLoggingEnabled()) {
+            SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
+                    caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
         }
     }
 
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
new file mode 100644
index 0000000..b5cf986
--- /dev/null
+++ b/services/tests/appfunctions/Android.bp
@@ -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 {
+    // 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: "FrameworksAppFunctionsTests",
+    team: "trendy_team_machine_learning",
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.ext.truth",
+        "platform-test-annotations",
+        "services.appfunctions",
+        "servicestests-core-utils",
+        "truth",
+        "frameworks-base-testutils",
+        "androidx.test.rules",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/appfunctions/AndroidManifest.xml b/services/tests/appfunctions/AndroidManifest.xml
new file mode 100644
index 0000000..1d42b17
--- /dev/null
+++ b/services/tests/appfunctions/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.appfunctionstests">
+
+    <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.frameworks.appfunctionstests"
+        android:label="Frameworks AppFunctions Services Tests" />
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/appfunctions/AndroidTest.xml b/services/tests/appfunctions/AndroidTest.xml
new file mode 100644
index 0000000..0650120
--- /dev/null
+++ b/services/tests/appfunctions/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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 Frameworks AppFunctions Services Tests.">
+    <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="FrameworksAppFunctionsTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksAppFunctionsTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.appfunctionstests" />
+        <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/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt
new file mode 100644
index 0000000..d8ce393
--- /dev/null
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appfunctions
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AppFunctionStaticMetadataHelperTest {
+
+    @Test
+    fun getStaticSchemaNameForPackage() {
+        val actualSchemaName =
+            AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage("com.example.app")
+
+        assertThat(actualSchemaName).isEqualTo("AppFunctionStaticMetadata-com.example.app")
+    }
+
+    @Test
+    fun getDocumentIdForAppFunction() {
+        val packageName = "com.example.app"
+        val functionId = "someFunction"
+
+        val actualDocumentId =
+            AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction(packageName, functionId)
+
+        assertThat(actualDocumentId).isEqualTo("com.example.app/someFunction")
+    }
+
+    @Test
+    fun getStaticMetadataQualifiedId() {
+        val packageName = "com.example.app"
+        val functionId = "someFunction"
+
+        val actualQualifiedId =
+            AppFunctionStaticMetadataHelper.getStaticMetadataQualifiedId(packageName, functionId)
+
+        assertThat(actualQualifiedId)
+            .isEqualTo("android\$apps-db/app_functions#com.example.app/someFunction")
+    }
+}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
new file mode 100644
index 0000000..5233f19
--- /dev/null
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appfunctions
+
+import android.app.appfunctions.AppFunctionRuntimeMetadata
+import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema
+import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema
+import android.app.appsearch.AppSearchManager
+import android.app.appsearch.PutDocumentsRequest
+import android.app.appsearch.SearchSpec
+import android.app.appsearch.SetSchemaRequest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class FutureAppSearchSessionTest {
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
+    private val testExecutor = MoreExecutors.directExecutor()
+
+    @Before
+    @After
+    fun clearData() {
+        val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+        FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use {
+            val setSchemaRequest = SetSchemaRequest.Builder().build()
+            it.setSchema(setSchemaRequest)
+        }
+    }
+
+    @Test
+    fun setSchema() {
+        val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+        FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
+            val setSchemaRequest =
+                SetSchemaRequest.Builder()
+                    .addSchemas(
+                        createParentAppFunctionRuntimeSchema(),
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                    )
+                    .build()
+
+            val schema = session.setSchema(setSchemaRequest)
+
+            assertThat(schema.get()).isNotNull()
+        }
+    }
+
+    @Test
+    fun put() {
+        val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+        FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
+            val setSchemaRequest =
+                SetSchemaRequest.Builder()
+                    .addSchemas(
+                        createParentAppFunctionRuntimeSchema(),
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                    )
+                    .build()
+            val schema = session.setSchema(setSchemaRequest)
+            assertThat(schema.get()).isNotNull()
+            val appFunctionRuntimeMetadata =
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+            val putDocumentsRequest: PutDocumentsRequest =
+                PutDocumentsRequest.Builder()
+                    .addGenericDocuments(appFunctionRuntimeMetadata)
+                    .build()
+
+            val putResult = session.put(putDocumentsRequest)
+
+            assertThat(putResult.get().isSuccess).isTrue()
+        }
+    }
+
+    @Test
+    fun search() {
+        val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+        FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
+            val setSchemaRequest =
+                SetSchemaRequest.Builder()
+                    .addSchemas(
+                        createParentAppFunctionRuntimeSchema(),
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                    )
+                    .build()
+            val schema = session.setSchema(setSchemaRequest)
+            assertThat(schema.get()).isNotNull()
+            val appFunctionRuntimeMetadata =
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+            val putDocumentsRequest: PutDocumentsRequest =
+                PutDocumentsRequest.Builder()
+                    .addGenericDocuments(appFunctionRuntimeMetadata)
+                    .build()
+            val putResult = session.put(putDocumentsRequest)
+            assertThat(putResult.get().isSuccess).isTrue()
+
+            val searchResult = session.search("", SearchSpec.Builder().build())
+
+            val genericDocs =
+                searchResult.get().nextPage.get().stream().map { it.genericDocument }.toList()
+            assertThat(genericDocs).hasSize(1)
+            val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genericDocs[0])
+            assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID)
+        }
+    }
+
+    private companion object {
+        const val TEST_DB: String = "test_db"
+        const val TEST_PACKAGE_NAME: String = "test_pkg"
+        const val TEST_FUNCTION_ID: String = "print"
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 5bb8ded..52f1cbd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -107,6 +107,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -3053,6 +3054,74 @@
     }
 
     @Test
+    public void testBrightnessUpdates() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        final float invalidBrightness = -0.3f;
+        final float brightnessOff = -1.0f;
+        final float minimumBrightness = 0.0f;
+        final float validBrightness = 0.5f;
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        // set and check valid brightness
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(validBrightness,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        // set and check invalid brightness
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, invalidBrightness);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(PowerManager.BRIGHTNESS_MIN,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        // reset and check valid brightness
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(validBrightness,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        // set and check brightness off
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightnessOff);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(PowerManager.BRIGHTNESS_MIN,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        // reset and check valid brightness
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(validBrightness,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        // set and check minimum brightness
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, minimumBrightness);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(PowerManager.BRIGHTNESS_MIN,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+    }
+
+    @Test
     public void testResolutionChangeGetsBackedUp() throws Exception {
         when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true);
         DisplayManagerService displayManager =
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index f9dc122..da79f30 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -226,7 +226,7 @@
         float clampedBrightness = 0.6f;
         float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
         when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(false);
         mTestInjector.mCapturedChangeListener.onChanged();
@@ -250,7 +250,7 @@
         float clampedBrightness = 0.6f;
         float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
         when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
@@ -274,7 +274,7 @@
         float clampedBrightness = 0.8f;
         float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
         when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
@@ -298,7 +298,7 @@
         float clampedBrightness = 0.6f;
         float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
         when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
deleted file mode 100644
index 9d16594..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
+++ /dev/null
@@ -1,312 +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.display.brightness.clamper;
-
-import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.hardware.display.DisplayManager;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.Temperature;
-import android.provider.DeviceConfig;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.Keep;
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
-import com.android.server.display.config.SensorData;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
-import com.android.server.testutils.FakeDeviceConfigInterface;
-import com.android.server.testutils.TestHandler;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(JUnitParamsRunner.class)
-public class BrightnessThermalClamperTest {
-
-    private static final float FLOAT_TOLERANCE = 0.001f;
-
-    private static final String DISPLAY_ID = "displayId";
-    @Mock
-    private IThermalService mMockThermalService;
-    @Mock
-    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
-
-    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
-            new FakeDeviceConfigInterface();
-    private final TestHandler mTestHandler = new TestHandler(null);
-    private BrightnessThermalClamper mClamper;
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
-                mMockClamperChangeListener, new TestThermalData());
-        mTestHandler.flush();
-    }
-
-    @Test
-    public void testTypeIsThermal() {
-        assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
-    }
-
-    @Test
-    public void testNoThrottlingData() {
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Keep
-    private static Object[][] testThrottlingData() {
-        // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
-        return new Object[][] {
-                // no throttling data
-                {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
-                // throttlingStatus < min throttling data
-                {List.of(
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
-                        Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
-                // throttlingStatus = min throttling data
-                {List.of(
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
-                        Temperature.THROTTLING_MODERATE, true, 0.5f},
-                // throttlingStatus between min and max throttling data
-                {List.of(
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
-                        Temperature.THROTTLING_SEVERE, true, 0.5f},
-                // throttlingStatus = max throttling data
-                {List.of(
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
-                        Temperature.THROTTLING_CRITICAL, true, 0.1f},
-                // throttlingStatus > max throttling data
-                {List.of(
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
-                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
-                        Temperature.THROTTLING_EMERGENCY, true, 0.1f},
-        };
-    }
-    @Test
-    @Parameters(method = "testThrottlingData")
-    public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
-            @Temperature.ThrottlingStatus int throttlingStatus,
-            boolean expectedActive, float expectedBrightness) throws RemoteException {
-        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
-        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
-        mTestHandler.flush();
-        assertEquals(expectedActive, mClamper.isActive());
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    @Parameters(method = "testThrottlingData")
-    public void testOnDisplayChangeAfterNotifyThrottling(List<ThrottlingLevel> throttlingLevels,
-            @Temperature.ThrottlingStatus int throttlingStatus,
-            boolean expectedActive, float expectedBrightness) throws RemoteException {
-        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
-        thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
-        mTestHandler.flush();
-        assertEquals(expectedActive, mClamper.isActive());
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testOverrideData() throws RemoteException {
-        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
-        thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        mClamper.onDisplayChanged(new TestThermalData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
-        mTestHandler.flush();
-        assertTrue(mClamper.isActive());
-        assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        overrideThrottlingData("displayId,1,emergency,0.4");
-        mClamper.onDeviceConfigChanged();
-        mTestHandler.flush();
-
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        overrideThrottlingData("displayId,1,moderate,0.4");
-        mClamper.onDeviceConfigChanged();
-        mTestHandler.flush();
-
-        assertTrue(mClamper.isActive());
-        assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testDisplaySensorBasedThrottling() throws RemoteException {
-        final int severity = PowerManager.THERMAL_STATUS_SEVERE;
-        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
-        // Update config to listen to display type sensor.
-        final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
-        final TestThermalData thermalData =
-                    new TestThermalData(
-                        DISPLAY_ID,
-                        DisplayDeviceConfig.DEFAULT_ID,
-                        List.of(new ThrottlingLevel(severity, 0.5f)),
-                        tempSensor);
-        mClamper.onDisplayChanged(thermalData);
-        mTestHandler.flush();
-        verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener);
-        thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY);
-        assertFalse(mClamper.isActive());
-
-        // Verify no throttling triggered when any other sensor notification received.
-        thermalEventListener.notifyThrottling(createSkinTemperature(severity));
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-
-        thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity));
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        // Verify throttling triggered when display sensor of given name throttled.
-        thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity));
-        mTestHandler.flush();
-        assertTrue(mClamper.isActive());
-        assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    private IThermalEventListener captureSkinThermalEventListener() throws RemoteException {
-        return captureThermalEventListener(Temperature.TYPE_SKIN);
-    }
-
-    private IThermalEventListener captureThermalEventListener(int type) throws RemoteException {
-        ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
-                IThermalEventListener.class);
-        verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
-                type));
-        return captor.getValue();
-    }
-
-    private Temperature createDisplayTemperature(
-                @NonNull String sensorName, @Temperature.ThrottlingStatus int status) {
-        return new Temperature(100, Temperature.TYPE_DISPLAY, sensorName, status);
-    }
-
-    private Temperature createSkinTemperature(@Temperature.ThrottlingStatus int status) {
-        return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
-    }
-
-    private void overrideThrottlingData(String data) {
-        mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
-    }
-
-    private class TestInjector extends BrightnessThermalClamper.Injector {
-        @Override
-        IThermalService getThermalService() {
-            return mMockThermalService;
-        }
-
-        @Override
-        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
-            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
-        }
-    }
-
-    private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
-
-        private final String mUniqueDisplayId;
-        private final String mDataId;
-        private final ThermalBrightnessThrottlingData mData;
-        private final SensorData mTempSensor;
-
-        private TestThermalData() {
-            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null,
-                    SensorData.loadTempSensorUnspecifiedConfig());
-        }
-
-        private TestThermalData(List<ThrottlingLevel> data) {
-            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data,
-                    SensorData.loadTempSensorUnspecifiedConfig());
-        }
-
-        private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data,
-                    SensorData tempSensor) {
-            mUniqueDisplayId = uniqueDisplayId;
-            mDataId = dataId;
-            mData = ThermalBrightnessThrottlingData.create(data);
-            mTempSensor = tempSensor;
-        }
-
-        @NonNull
-        @Override
-        public String getUniqueDisplayId() {
-            return mUniqueDisplayId;
-        }
-
-        @NonNull
-        @Override
-        public String getThermalThrottlingDataId() {
-            return mDataId;
-        }
-
-        @Nullable
-        @Override
-        public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
-            return mData;
-        }
-
-        @NonNull
-        @Override
-        public SensorData getTempSensor() {
-            return mTempSensor;
-        }
-    }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java
new file mode 100644
index 0000000..35d384b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
+import com.android.server.display.config.SensorData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessThermalModifierTest {
+    private static final int NO_MODIFIER = 0;
+
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private static final String DISPLAY_ID = "displayId";
+    @Mock
+    private IThermalService mMockThermalService;
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+    @Mock
+    private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+    @Mock
+    private DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock
+    private IBinder mMockBinder;
+
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private BrightnessThermalModifier mModifier;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockDisplayDeviceConfig.getTempSensor())
+                .thenReturn(SensorData.loadTempSensorUnspecifiedConfig());
+        mModifier = new BrightnessThermalModifier(new TestInjector(), mTestHandler,
+                mMockClamperChangeListener,
+                ClamperTestUtilsKt.createDisplayDeviceData(mMockDisplayDeviceConfig, mMockBinder));
+        mTestHandler.flush();
+    }
+
+
+    @Test
+    public void testNoThrottlingData() {
+        assertModifierState(
+                0.3f, true,
+                PowerManager.BRIGHTNESS_MAX, 0.3f,
+                false, true);
+    }
+
+    @Keep
+    private static Object[][] testThrottlingData() {
+        // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
+        return new Object[][] {
+                // no throttling data
+                {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus < min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus = min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_MODERATE, true, 0.5f},
+                // throttlingStatus between min and max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_SEVERE, true, 0.5f},
+                // throttlingStatus = max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_CRITICAL, true, 0.1f},
+                // throttlingStatus > max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_EMERGENCY, true, 0.1f},
+        };
+    }
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        onDisplayChange(throttlingLevels);
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                expectedBrightness, expectedBrightness,
+                expectedActive, !expectedActive);
+    }
+
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testOnDisplayChangeAfterNotifyThrottling(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
+        mTestHandler.flush();
+
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        onDisplayChange(throttlingLevels);
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                expectedBrightness, expectedBrightness,
+                expectedActive, !expectedActive);
+    }
+
+    @Test
+    public void testAppliesFastChangeOnlyOnActivation() throws RemoteException  {
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f)));
+        mTestHandler.flush();
+
+        thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
+        mTestHandler.flush();
+
+        // expectedSlowChange = false
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                0.5f, 0.5f,
+                true, false);
+
+        // slowChange is unchanged
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                0.5f, 0.5f,
+                true, true);
+    }
+
+    @Test
+    public void testCapsMaxBrightnessOnly_currentBrightnessIsLowAndFastChange()
+            throws RemoteException {
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f)));
+        mTestHandler.flush();
+
+        thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
+        mTestHandler.flush();
+
+        assertModifierState(
+                0.1f, false,
+                0.5f, 0.1f,
+                true, false);
+    }
+
+    @Test
+    public void testOverrideData() throws RemoteException {
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
+        mTestHandler.flush();
+
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f)));
+        mTestHandler.flush();
+
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                0.5f, 0.5f,
+                true, false);
+
+        overrideThrottlingData("displayId,1,emergency,0.4");
+        mModifier.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        overrideThrottlingData("displayId,1,moderate,0.4");
+        mModifier.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                0.4f, 0.4f,
+                true, false);
+    }
+
+    @Test
+    public void testDisplaySensorBasedThrottling() throws RemoteException {
+        final int severity = PowerManager.THERMAL_STATUS_SEVERE;
+        IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+        // Update config to listen to display type sensor.
+        SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+
+        when(mMockDisplayDeviceConfig.getTempSensor()).thenReturn(tempSensor);
+        onDisplayChange(List.of(new ThrottlingLevel(severity, 0.5f)));
+        mTestHandler.flush();
+
+        verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener);
+        thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY);
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        // Verify no throttling triggered when any other sensor notification received.
+        thermalEventListener.notifyThrottling(createSkinTemperature(severity));
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity));
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX,
+                false, true);
+
+        // Verify throttling triggered when display sensor of given name throttled.
+        thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity));
+        mTestHandler.flush();
+        assertModifierState(
+                PowerManager.BRIGHTNESS_MAX, true,
+                0.5f, 0.5f,
+                true, false);
+    }
+
+    private IThermalEventListener captureSkinThermalEventListener() throws RemoteException {
+        return captureThermalEventListener(Temperature.TYPE_SKIN);
+    }
+
+    private IThermalEventListener captureThermalEventListener(int type) throws RemoteException {
+        ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
+                IThermalEventListener.class);
+        verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
+                type));
+        return captor.getValue();
+    }
+
+    private Temperature createDisplayTemperature(
+                @NonNull String sensorName, @Temperature.ThrottlingStatus int status) {
+        return new Temperature(100, Temperature.TYPE_DISPLAY, sensorName, status);
+    }
+
+    private Temperature createSkinTemperature(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
+    }
+
+    private void overrideThrottlingData(String data) {
+        mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
+    }
+
+    private void onDisplayChange(List<ThrottlingLevel> throttlingLevels) {
+        Map<String, ThermalBrightnessThrottlingData> throttlingLevelsMap = new HashMap<>();
+        throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID,
+                ThermalBrightnessThrottlingData.create(throttlingLevels));
+        when(mMockDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId())
+                .thenReturn(throttlingLevelsMap);
+        mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData(
+                mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID));
+    }
+
+    private void assertModifierState(
+            float currentBrightness,
+            boolean currentSlowChange,
+            float maxBrightness, float brightness,
+            boolean isActive,
+            boolean isSlowChange) {
+        ModifiersAggregatedState modifierState = new ModifiersAggregatedState();
+        DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder();
+        stateBuilder.setBrightness(currentBrightness);
+        stateBuilder.setIsSlowChange(currentSlowChange);
+
+        int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL
+                : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+        int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER;
+
+        mModifier.applyStateChange(modifierState);
+        assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness);
+        assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason);
+
+        mModifier.apply(mMockRequest, stateBuilder);
+
+        assertThat(stateBuilder.getMaxBrightness()).isWithin(FLOAT_TOLERANCE).of(maxBrightness);
+        assertThat(stateBuilder.getBrightness()).isWithin(FLOAT_TOLERANCE).of(brightness);
+        assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason);
+        assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier);
+        assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange);
+    }
+
+
+    private class TestInjector extends BrightnessThermalModifier.Injector {
+        @Override
+        IThermalService getThermalService() {
+            return mMockThermalService;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
index 5fd848f..f21749e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt
@@ -21,6 +21,7 @@
 import com.android.server.display.DisplayDeviceConfig
 import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData
 
+@JvmOverloads
 fun createDisplayDeviceData(
     displayDeviceConfig: DisplayDeviceConfig,
     displayToken: IBinder,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e9..e5005d1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,8 @@
 import android.graphics.PointF;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -56,6 +58,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
 
@@ -119,6 +122,9 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     /**
      * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
      * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -154,18 +160,43 @@
         mHandler = new TestHandler();
         mTouchExplorer = new TouchExplorer(mContext, mMockAms, null, mHandler);
         mTouchExplorer.setNext(mCaptor);
+        // Start TouchExplorer in the state where it has already reset InputDispatcher so that
+        // all tests do not start with an irrelevant ACTION_CANCEL.
+        mTouchExplorer.setHasResetInputDispatcherState(true);
     }
 
     @Test
     public void testOneFingerMove_shouldInjectHoverEvents() {
-        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        // Wait for transiting to touch exploring state.
+        triggerTouchExplorationWithOneFingerDownMoveUp();
+        assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        assertState(STATE_TOUCH_EXPLORING);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESET_INPUT_DISPATCHER_BEFORE_FIRST_TOUCH_EXPLORATION)
+    public void testStartTouchExploration_shouldResetInputDispatcherStateWithActionCancel() {
+        // Start TouchExplorer in the state where it has *not yet* reset InputDispatcher.
+        mTouchExplorer.setHasResetInputDispatcherState(false);
+        // Trigger touch exploration twice, with a handler fast-forward in between so TouchExplorer
+        // treats these as two separate interactions.
+        triggerTouchExplorationWithOneFingerDownMoveUp();
+        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+        triggerTouchExplorationWithOneFingerDownMoveUp();
+
+        assertCapturedEvents(
+                ACTION_CANCEL, // Only one ACTION_CANCEL before the first touch exploration
+                ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT,
+                ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        assertState(STATE_TOUCH_EXPLORING);
+    }
+
+    private void triggerTouchExplorationWithOneFingerDownMoveUp() {
+        send(downEvent());
+        // Fast forward so that TouchExplorer's timeouts transition us to the touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
         moveEachPointers(mLastEvent, p(10, 10));
         send(mLastEvent);
-        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
-        assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
-        assertState(STATE_TOUCH_EXPLORING);
+        send(upEvent());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index 6ace9f1..fca0cfb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
+import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
@@ -582,6 +583,9 @@
         );
         mTestLooper.dispatchAll();
 
+        mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).contains(
                 HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
                         getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
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 6731403..ec44a91 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -17,6 +17,7 @@
 package com.android.server.hdmi;
 
 import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -223,6 +224,9 @@
         );
         mTestLooper.dispatchAll();
 
+        mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).contains(
                 HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
                         getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f0850af..51e0c33 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1456,21 +1456,21 @@
     public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA;
 
     /**
-     * Bitmask for {@code SERVICE_CAPABILITY_VOICE}.
+     * Bitmask for {@link #SERVICE_CAPABILITY_VOICE}.
      * @hide
      */
     public static final int SERVICE_CAPABILITY_VOICE_BITMASK =
             serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE);
 
     /**
-     * Bitmask for {@code SERVICE_CAPABILITY_SMS}.
+     * Bitmask for {@link #SERVICE_CAPABILITY_SMS}.
      * @hide
      */
     public static final int SERVICE_CAPABILITY_SMS_BITMASK =
             serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS);
 
     /**
-     * Bitmask for {@code SERVICE_CAPABILITY_DATA}.
+     * Bitmask for {@link #SERVICE_CAPABILITY_DATA}.
      * @hide
      */
     public static final int SERVICE_CAPABILITY_DATA_BITMASK =
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3ff1e2c..3e226cc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6907,7 +6907,6 @@
         return false;
     }
 
-    // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
     /**
      * @return true if the current device is "voice capable".
      * <p>
@@ -6921,10 +6920,10 @@
      * PackageManager.FEATURE_TELEPHONY system feature, which is available
      * on any device with a telephony radio, even if the device is
      * data-only.
-     * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice
+     * @deprecated Replaced by {@link #isDeviceVoiceCapable()}. Starting from Android 15, voice
      * capability may also be overridden by carriers for a given subscription. For voice capable
-     * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
-     * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
+     * device (when {@link #isDeviceVoiceCapable} return {@code true}), caller should check for
+     * subscription-level voice capability as well. See {@link #isDeviceVoiceCapable} for details.
      */
     @Deprecated
     public boolean isVoiceCapable() {
@@ -6946,8 +6945,8 @@
      * <p>
      * Starting from Android 15, voice capability may also be overridden by carrier for a given
      * subscription on a voice capable device. To check if a subscription is "voice capable",
-     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
-     * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
+     * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
@@ -6964,10 +6963,10 @@
      * <p>
      * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
      *       disabled when device doesn't support sms.
-     * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS
+     * @deprecated Replaced by {@link #isDeviceSmsCapable()}. Starting from Android 15, SMS
      * capability may also be overridden by carriers for a given subscription. For SMS capable
-     * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
-     * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
+     * device (when {@link #isDeviceSmsCapable} return {@code true}), caller should check for
+     * subscription-level SMS capability as well. See {@link #isDeviceSmsCapable} for details.
      */
     @Deprecated
     public boolean isSmsCapable() {
@@ -6986,8 +6985,8 @@
      * <p>
      * Starting from Android 15, SMS capability may also be overridden by carriers for a given
      * subscription on an SMS capable device. To check if a subscription is "SMS capable",
-     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
-     * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
+     * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@link SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 27cc923..189de6b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.protolog;
 
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +28,6 @@
 
 import static java.io.File.createTempFile;
 
-import android.content.Context;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.tools.ScenarioBuilder;
@@ -45,6 +42,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogDataType;
 import com.android.internal.protolog.common.LogLevel;
@@ -53,11 +51,11 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
 
 import perfetto.protos.Protolog;
 import perfetto.protos.ProtologCommon;
@@ -76,6 +74,7 @@
 @RunWith(JUnit4.class)
 public class PerfettoProtoLogImplTest {
     private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
+    private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb";
     private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
             .getTargetContext().getFilesDir();
 
@@ -92,29 +91,19 @@
             new TraceConfig(false, true, false)
     );
 
-    private ProtoLogConfigurationService mProtoLogConfigurationService;
-    private PerfettoProtoLogImpl mProtoLog;
-    private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder;
-    private File mFile;
-    private Runnable mCacheUpdater;
+    private static ProtoLogConfigurationService sProtoLogConfigurationService;
+    private static PerfettoProtoLogImpl sProtoLog;
+    private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder;
+    private static Runnable sCacheUpdater;
 
-    private ProtoLogViewerConfigReader mReader;
+    private static ProtoLogViewerConfigReader sReader;
 
     public PerfettoProtoLogImplTest() throws IOException {
     }
 
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        final Context testContext = getInstrumentation().getContext();
-        mFile = testContext.getFileStreamPath("tracing_test.dat");
-        //noinspection ResultOfMethodCallIgnored
-        mFile.delete();
-
-        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
-        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
-
-        mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
+    @BeforeClass
+    public static void setUp() throws Exception {
+        sViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
                 .addGroups(
                         Protolog.ProtoLogViewerConfig.Group.newBuilder()
                                 .setId(1)
@@ -160,33 +149,52 @@
         ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
                 ViewerConfigInputStreamProvider.class);
         Mockito.when(viewerConfigInputStreamProvider.getInputStream())
-                .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
+                .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()));
 
-        mCacheUpdater = () -> {};
-        mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+        sCacheUpdater = () -> {};
+        sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
 
         final ProtoLogDataSourceBuilder dataSourceBuilder =
                 (onStart, onFlush, onStop) -> new ProtoLogDataSource(
                         onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME);
-        mProtoLogConfigurationService =
-                new ProtoLogConfigurationService(dataSourceBuilder);
-        mProtoLog = new PerfettoProtoLogImpl(
-                viewerConfigInputStreamProvider, mReader, () -> mCacheUpdater.run(),
-                TestProtoLogGroup.values(), dataSourceBuilder, mProtoLogConfigurationService);
+        final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> {
+            Utils.dumpViewerConfig(dataSource, () -> {
+                if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) {
+                    throw new RuntimeException(
+                            "Unexpected viewer config file path provided");
+                }
+                return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+            });
+        };
+        sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer);
+
+        if (android.tracing.Flags.clientSideProtoLogging()) {
+            sProtoLog = new PerfettoProtoLogImpl(
+                    MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(),
+                    TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
+        } else {
+            sProtoLog = new PerfettoProtoLogImpl(
+                    viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
+                    TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
+        }
+    }
+
+    @Before
+    public void before() {
+        Mockito.reset(sReader);
+
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
     }
 
     @After
     public void tearDown() {
-        if (mFile != null) {
-            //noinspection ResultOfMethodCallIgnored
-            mFile.delete();
-        }
         ProtoLogImpl.setSingleInstance(null);
     }
 
     @Test
     public void isEnabled_returnsFalseByDefault() {
-        assertFalse(mProtoLog.isProtoEnabled());
+        assertFalse(sProtoLog.isProtoEnabled());
     }
 
     @Test
@@ -196,7 +204,7 @@
                 .build();
         try {
             traceMonitor.start();
-            assertTrue(mProtoLog.isProtoEnabled());
+            assertTrue(sProtoLog.isProtoEnabled());
         } finally {
             traceMonitor.stop(mWriter);
         }
@@ -209,12 +217,12 @@
                 .build();
         try {
             traceMonitor.start();
-            assertTrue(mProtoLog.isProtoEnabled());
+            assertTrue(sProtoLog.isProtoEnabled());
         } finally {
             traceMonitor.stop(mWriter);
         }
 
-        assertFalse(mProtoLog.isProtoEnabled());
+        assertFalse(sProtoLog.isProtoEnabled());
     }
 
     @Test
@@ -226,15 +234,15 @@
             traceMonitor.start();
             // Shouldn't be logging anything except WTF unless explicitly requested in the group
             // override.
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+            sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+            sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+            sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+            sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -258,15 +266,15 @@
                 ).build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+            sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+            sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+            sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+            sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -294,15 +302,15 @@
                     ).build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+            sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+            sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+            sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+            sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -324,15 +332,15 @@
                 .build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+            sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+            sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+            sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
                     LogDataType.BOOLEAN, new Object[]{true});
-            mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+            sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -351,8 +359,8 @@
 
     @Test
     public void log_logcatEnabled() {
-        when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
-        PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
+        PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
@@ -363,13 +371,13 @@
         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
                 LogLevel.INFO),
                 eq("test true 10000 % 0x7530 test 3.0E-6"));
-        verify(mReader).getViewerString(eq(1234L));
+        verify(sReader).getViewerString(eq(1234L));
     }
 
     @Test
     public void log_logcatEnabledInvalidMessage() {
-        when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
-        PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
+        PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
@@ -381,29 +389,32 @@
                 LogLevel.INFO),
                 eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
                         + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
-        verify(mReader).getViewerString(eq(1234L));
+        verify(sReader).getViewerString(eq(1234L));
     }
 
     @Test
     public void log_logcatEnabledNoMessage() {
-        when(mReader.getViewerString(anyLong())).thenReturn(null);
-        PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        when(sReader.getViewerString(anyLong())).thenReturn(null);
+        PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
-        implSpy.log(
+        var assertion = assertThrows(RuntimeException.class, () -> implSpy.log(
                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
-                new Object[]{5});
+                new Object[]{5}));
 
-        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
-                LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
-        verify(mReader).getViewerString(eq(1234L));
+        verify(implSpy, never()).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+                LogLevel.INFO), any());
+        verify(sReader).getViewerString(eq(1234L));
+
+        Truth.assertThat(assertion).hasMessageThat()
+                .contains("Failed to get log message with hash 1234 and args (5)");
     }
 
     @Test
     public void log_logcatDisabled() {
-        when(mReader.getViewerString(anyLong())).thenReturn("test %d");
-        PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        when(sReader.getViewerString(anyLong())).thenReturn("test %d");
+        PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
 
         implSpy.log(
@@ -411,7 +422,7 @@
                 new Object[]{5});
 
         verify(implSpy, never()).passToLogcat(any(), any(), any());
-        verify(mReader, never()).getViewerString(anyLong());
+        verify(sReader, never()).getViewerString(anyLong());
     }
 
     @Test
@@ -426,11 +437,12 @@
         long before;
         long after;
         try {
+            assertFalse(sProtoLog.isProtoEnabled());
             traceMonitor.start();
-            assertTrue(mProtoLog.isProtoEnabled());
+            assertTrue(sProtoLog.isProtoEnabled());
 
             before = SystemClock.elapsedRealtimeNanos();
-            mProtoLog.log(
+            sProtoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
                     0b1110101001010100,
                     new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
@@ -448,7 +460,8 @@
         Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
                 .isAtMost(after);
         Truth.assertThat(protolog.messages.getFirst().getMessage())
-                .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true");
+                .isEqualTo(
+                        "My test message :: test, 1, 2, 3, 0.400000, 5.000000e-01, 0.6, true");
     }
 
     @Test
@@ -460,10 +473,10 @@
         long after;
         try {
             traceMonitor.start();
-            assertTrue(mProtoLog.isProtoEnabled());
+            assertTrue(sProtoLog.isProtoEnabled());
 
             before = SystemClock.elapsedRealtimeNanos();
-            mProtoLog.log(
+            sProtoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
                     "My test message :: %s, %d, %x, %f, %b",
                     "test", 1, 3, 0.4, true);
@@ -481,7 +494,7 @@
         Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
                 .isAtMost(after);
         Truth.assertThat(protolog.messages.getFirst().getMessage())
-                .isEqualTo("My test message :: test, 2, 6, 0.400000, true");
+                .isEqualTo("My test message :: test, 1, 3, 0.400000, true");
     }
 
     @Test
@@ -491,7 +504,7 @@
                 .build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -507,7 +520,7 @@
 
     private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
         final long messageId = new Random().nextLong();
-        mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+        sViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                 .setMessageId(messageId)
                 .setMessage(message)
                 .setLevel(logLevel)
@@ -530,7 +543,7 @@
         try {
             traceMonitor.start();
             before = SystemClock.elapsedRealtimeNanos();
-            mProtoLog.log(
+            sProtoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
                     0b01100100,
                     new Object[]{"test", 1, 0.1, true});
@@ -550,7 +563,7 @@
                 .build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     0b11, new Object[]{true});
         } finally {
             traceMonitor.stop(mWriter);
@@ -575,7 +588,7 @@
         try {
             traceMonitor.start();
 
-            ProtoLogImpl.setSingleInstance(mProtoLog);
+            ProtoLogImpl.setSingleInstance(sProtoLog);
             ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
                     0b11, true);
         } finally {
@@ -599,7 +612,7 @@
     @Test
     public void cacheIsUpdatedWhenTracesStartAndStop() {
         final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0);
-        mCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+        sCacheUpdater = cacheUpdateCallCount::incrementAndGet;
 
         PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
                 .enableProtoLog(true,
@@ -641,17 +654,17 @@
 
     @Test
     public void isEnabledUpdatesBasedOnRunningTraces() {
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
 
         PerfettoTraceMonitor traceMonitor1 =
                 PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
@@ -670,65 +683,65 @@
         try {
             traceMonitor1.start();
 
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
                     .isTrue();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
                     .isTrue();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
                     .isTrue();
 
             try {
                 traceMonitor2.start();
 
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
                         .isTrue();
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
                         LogLevel.VERBOSE)).isTrue();
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
                         .isTrue();
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
                         .isTrue();
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
                         .isTrue();
-                Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+                Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
                         .isTrue();
             } finally {
                 traceMonitor2.stop(mWriter);
             }
 
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
                     .isFalse();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
                     .isTrue();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
                     .isTrue();
-            Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+            Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
                     .isTrue();
         } finally {
             traceMonitor1.stop(mWriter);
         }
 
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
                 .isFalse();
-        Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+        Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
                 .isFalse();
     }
 
@@ -741,7 +754,7 @@
         try {
             traceMonitor.start();
 
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
                     "My test null string: %s", (Object) null);
         } finally {
             traceMonitor.stop(mWriter);
@@ -764,7 +777,7 @@
         try {
             traceMonitor.start();
 
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
                     "My null args: %d, %f, %b", null, null, null);
         } finally {
             traceMonitor.stop(mWriter);
@@ -775,7 +788,7 @@
 
         Truth.assertThat(protolog.messages).hasSize(1);
         Truth.assertThat(protolog.messages.get(0).getMessage())
-                .isEqualTo("My null args: 0, 0, false");
+                .isEqualTo("My null args: 0, 0.000000, false");
     }
 
     @Test
@@ -798,7 +811,7 @@
             traceMonitor1.start();
             traceMonitor2.start();
 
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
                     LogDataType.BOOLEAN, new Object[]{true});
         } finally {
             traceMonitor1.stop(mWriter);
@@ -827,12 +840,12 @@
                 .build();
         try {
             traceMonitor.start();
-            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
-                "This message should not be logged");
-            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
-                "This message should logged %d", 123);
-            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
-                "This message should also be logged %d", 567);
+            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+                    "This message should not be logged");
+            sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+                    "This message should be logged %d", 123);
+            sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+                    "This message should also be logged %d", 567);
         } finally {
             traceMonitor.stop(mWriter);
         }
@@ -845,7 +858,7 @@
         Truth.assertThat(protolog.messages.get(0).getLevel())
                 .isEqualTo(LogLevel.WARN);
         Truth.assertThat(protolog.messages.get(0).getMessage())
-                .isEqualTo("This message should logged 123");
+                .isEqualTo("This message should be logged 123");
 
         Truth.assertThat(protolog.messages.get(1).getLevel())
                 .isEqualTo(LogLevel.ERROR);
@@ -853,6 +866,19 @@
                 .isEqualTo("This message should also be logged 567");
     }
 
+    @Test
+    public void throwsOnLogToLogcatForProcessedMessageMissingLoadedDefinition() {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        var protolog = new PerfettoProtoLogImpl(TestProtoLogGroup.values());
+
+        var exception = assertThrows(RuntimeException.class, () -> {
+            protolog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 123, 0, new Object[0]);
+        });
+
+        Truth.assertThat(exception).hasMessageThat()
+                .contains("Failed to get log message with hash 123");
+    }
+
     private enum TestProtoLogGroup implements IProtoLogGroup {
         TEST_GROUP(true, true, false, "TEST_TAG");
 
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 887630b..b5cc553 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -59,7 +59,6 @@
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.os.test.TestLooper;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -73,10 +72,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.telephony.flags.Flags;
-
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -133,8 +129,6 @@
         TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap);
     }
 
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
 
     @NonNull private final Context mContext;
     @NonNull private final TestLooper mTestLooper;
@@ -193,7 +187,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CRASH_ON_GETTING_CONFIG_WHEN_PHONE_IS_GONE);
         doReturn(2).when(mTelephonyManager).getActiveModemCount();
 
         mCallback = mock(TelephonySubscriptionTrackerCallback.class);
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
index 0115339..245e802 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -53,7 +53,7 @@
                         .setMessageId(key)
                         .setMessage(log.messageString)
                         .setLevel(
-                            ProtoLogLevel.forNumber(log.logLevel.ordinal + 1))
+                            ProtoLogLevel.forNumber(log.logLevel.id))
                         .setGroupId(groupId)
                         .setLocation(log.position)
             )