Merge "Implement a command handler for the ProtoLog service" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 3620a11..7d9e95b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1108,6 +1108,7 @@
 // Chooser / "Sharesheet"
 aconfig_declarations {
     name: "android.service.chooser.flags-aconfig",
+    exportable: true,
     package: "android.service.chooser",
     container: "system",
     srcs: ["core/java/android/service/chooser/flags.aconfig"],
diff --git a/api/Android.bp b/api/Android.bp
index d931df1..341be3d53 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -284,7 +284,7 @@
 // These are libs from framework-internal-utils that are required (i.e. being referenced)
 // from framework-non-updatable-sources. Add more here when there's a need.
 // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
-// dependencies gets bigger.
+// dependencies when the list gets bigger.
 android_non_updatable_stubs_libs = [
     "android.hardware.cas-V1.2-java",
     "android.hardware.health-V1.0-java-constants",
@@ -384,6 +384,11 @@
     "sdk_system_current_android",
 ]
 
+java_defaults {
+    name: "module-classpath-java-defaults",
+    libs: non_updatable_api_deps_on_modules,
+}
+
 // Defaults with module APIs in the classpath (mostly from prebuilts).
 // Suitable for compiling android-non-updatable.
 stubs_defaults {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 8dfddf0..d991da5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -563,8 +563,12 @@
 
 java_defaults {
     name: "android-non-updatable_from_text_defaults",
+    defaults: ["android-non-updatable-stubs-libs-defaults"],
     static_libs: ["framework-res-package-jar"],
     libs: ["stub-annotations"],
+    sdk_version: "none",
+    system_modules: "none",
+    previous_api: ":android.api.public.latest",
 }
 
 java_defaults {
@@ -582,10 +586,10 @@
         "api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.public.latest",
+    libs: ["all-modules-public-stubs"],
 }
 
 java_api_library {
@@ -596,10 +600,10 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_system_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.system.latest",
+    libs: ["all-modules-system-stubs"],
 }
 
 java_api_library {
@@ -611,10 +615,10 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_test_stubs_current.from-text",
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.test.latest",
+    libs: ["all-modules-system-stubs"],
 }
 
 java_api_library {
@@ -625,8 +629,10 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
+    defaults: [
+        "module-classpath-java-defaults",
+        "android-non-updatable_everything_from_text_defaults",
+    ],
     // Use full Android API not just the non-updatable API as the latter is incomplete
     // and can result in incorrect behavior.
     previous_api: ":android.api.combined.module-lib.latest",
@@ -644,14 +650,16 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
-    defaults: ["android-non-updatable_everything_from_text_defaults"],
-    full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+    defaults: [
+        "module-classpath-java-defaults",
+        "android-non-updatable_everything_from_text_defaults",
+    ],
 
     // No need to specify previous_api as this is not used for compiling against.
-
     // This module is only used for hiddenapi, and other modules should not
     // depend on this module.
     visibility: ["//visibility:private"],
+    libs: ["all-modules-system-stubs"],
 }
 
 java_defaults {
@@ -665,7 +673,7 @@
 }
 
 java_library {
-    name: "android_stubs_current.from-source",
+    name: "android_stubs_current",
     static_libs: [
         "all-modules-public-stubs",
         "android-non-updatable.stubs",
@@ -675,7 +683,7 @@
 }
 
 java_library {
-    name: "android_stubs_current_exportable.from-source",
+    name: "android_stubs_current_exportable",
     static_libs: [
         "all-modules-public-stubs-exportable",
         "android-non-updatable.stubs.exportable",
@@ -685,7 +693,7 @@
 }
 
 java_library {
-    name: "android_system_stubs_current.from-source",
+    name: "android_system_stubs_current",
     static_libs: [
         "all-modules-system-stubs",
         "android-non-updatable.stubs.system",
@@ -698,7 +706,7 @@
 }
 
 java_library {
-    name: "android_system_stubs_current_exportable.from-source",
+    name: "android_system_stubs_current_exportable",
     static_libs: [
         "all-modules-system-stubs-exportable",
         "android-non-updatable.stubs.exportable.system",
@@ -722,7 +730,7 @@
 }
 
 java_library {
-    name: "android_test_stubs_current.from-source",
+    name: "android_test_stubs_current",
     static_libs: [
         // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
         // include the SystemApi of framework-non-updatable-sources.
@@ -739,7 +747,7 @@
 }
 
 java_library {
-    name: "android_test_stubs_current_exportable.from-source",
+    name: "android_test_stubs_current_exportable",
     static_libs: [
         // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
         // include the SystemApi of framework-non-updatable-sources.
@@ -760,7 +768,7 @@
 
 // This module does not need to be copied to dist
 java_library {
-    name: "android_test_frameworks_core_stubs_current.from-source",
+    name: "android_test_frameworks_core_stubs_current",
     static_libs: [
         "all-updatable-modules-system-stubs",
         "android-non-updatable.stubs.test",
@@ -772,7 +780,7 @@
 }
 
 java_library {
-    name: "android_module_lib_stubs_current.from-source",
+    name: "android_module_lib_stubs_current",
     defaults: [
         "android.jar_defaults",
     ],
@@ -785,7 +793,7 @@
 }
 
 java_library {
-    name: "android_module_lib_stubs_current_exportable.from-source",
+    name: "android_module_lib_stubs_current_exportable",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -801,20 +809,20 @@
 }
 
 java_library {
-    name: "android_system_server_stubs_current.from-source",
+    name: "android_system_server_stubs_current",
     defaults: [
         "android.jar_defaults",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
-        "android_module_lib_stubs_current.from-source",
+        "android_module_lib_stubs_current",
     ],
     visibility: ["//frameworks/base/services"],
 }
 
 java_library {
-    name: "android_system_server_stubs_current_exportable.from-source",
+    name: "android_system_server_stubs_current_exportable",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -822,7 +830,7 @@
     srcs: [":services-non-updatable-stubs{.exportable}"],
     installable: false,
     static_libs: [
-        "android_module_lib_stubs_current_exportable.from-source",
+        "android_module_lib_stubs_current_exportable",
     ],
     dist: {
         dir: "apistubs/android/system-server",
@@ -897,215 +905,6 @@
     },
 }
 
-//
-// Java API defaults and libraries for single tree build
-//
-
-java_defaults {
-    name: "stub-annotation-defaults",
-    libs: [
-        "stub-annotations",
-    ],
-    static_libs: [
-        // stub annotations do not contribute to the API surfaces but are statically
-        // linked in the stubs for API surfaces (see frameworks/base/StubLibraries.bp).
-        // This is because annotation processors insist on loading the classes for any
-        // annotations found, thus should exist inside android.jar.
-        "private-stub-annotations-jar",
-    ],
-    is_stubs_module: true,
-}
-
-// Listing of API domains contribution and dependencies per API surfaces
-java_defaults {
-    name: "android_test_stubs_current_contributions",
-    api_surface: "test",
-    api_contributions: [
-        "framework-virtualization.stubs.source.test.api.contribution",
-        "framework-location.stubs.source.test.api.contribution",
-    ],
-}
-
-java_defaults {
-    name: "android_test_frameworks_core_stubs_current_contributions",
-    api_surface: "test",
-    api_contributions: [
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-}
-
-java_defaults {
-    name: "android_module_lib_stubs_current_contributions",
-    api_surface: "module-lib",
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "module-lib-api-stubs-docs-non-updatable.api.contribution",
-        "art.module.public.api.stubs.source.api.contribution",
-        "art.module.public.api.stubs.source.system.api.contribution",
-        "art.module.public.api.stubs.source.module_lib.api.contribution",
-        "i18n.module.public.api.stubs.source.api.contribution",
-        "i18n.module.public.api.stubs.source.system.api.contribution",
-        "i18n.module.public.api.stubs.source.module_lib.api.contribution",
-    ],
-    previous_api: ":android.api.combined.module-lib.latest",
-}
-
-// Java API library definitions per API surface
-java_api_library {
-    name: "android_stubs_current.from-text",
-    api_surface: "public",
-    defaults: [
-        // This module is dynamically created at frameworks/base/api/api.go
-        // instead of being written out, in order to minimize edits in the codebase
-        // when there is a change in the list of modules.
-        // that contributes to an api surface.
-        "android_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_system_stubs_current.from-text",
-    api_surface: "system",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_stubs_current.from-text",
-    api_surface: "test",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_stubs_current_contributions",
-        "stub-annotation-defaults",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_frameworks_core_stubs_current.from-text",
-    api_surface: "test",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_frameworks_core_stubs_current_contributions",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_module_lib_stubs_current_full.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_module_lib_stubs_current_contributions_full",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "api-stubs-docs-non-updatable.api.contribution",
-        "system-api-stubs-docs-non-updatable.api.contribution",
-        "module-lib-api-stubs-docs-non-updatable.api.contribution",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_module_lib_stubs_current.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_module_lib_stubs_current_contributions",
-    ],
-    libs: [
-        "android_module_lib_stubs_current_full.from-text",
-        "stub-annotations",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_test_module_lib_stubs_current.from-text",
-    api_surface: "module-lib",
-    defaults: [
-        "android_stubs_current_contributions",
-        "android_system_stubs_current_contributions",
-        "android_test_stubs_current_contributions",
-        "android_module_lib_stubs_current_contributions",
-    ],
-    libs: [
-        "android_module_lib_stubs_current_full.from-text",
-        "stub-annotations",
-    ],
-    api_contributions: [
-        "test-api-stubs-docs-non-updatable.api.contribution",
-    ],
-
-    // This module is only used to build android-non-updatable.stubs.test_module_lib
-    // and other modules should not depend on this module.
-    visibility: [
-        "//visibility:private",
-    ],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
-java_api_library {
-    name: "android_system_server_stubs_current.from-text",
-    api_surface: "system-server",
-    api_contributions: [
-        "services-non-updatable-stubs.api.contribution",
-    ],
-    libs: [
-        "android_module_lib_stubs_current.from-text",
-        "stub-annotations",
-    ],
-    static_libs: [
-        "android_module_lib_stubs_current.from-text",
-    ],
-    visibility: ["//visibility:public"],
-    enable_validation: false,
-    stubs_type: "everything",
-}
-
 ////////////////////////////////////////////////////////////////////////
 // api-versions.xml generation, for public and system. This API database
 // also contains the android.test.* APIs.
diff --git a/api/api.go b/api/api.go
index b6b1a7e..5b7f534 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,9 +15,7 @@
 package api
 
 import (
-	"fmt"
 	"sort"
-	"strings"
 
 	"github.com/google/blueprint/proptools"
 
@@ -464,79 +462,6 @@
 	}
 }
 
-func createApiContributionDefaults(ctx android.LoadHookContext, modules []string) {
-	defaultsSdkKinds := []android.SdkKind{
-		android.SdkPublic, android.SdkSystem, android.SdkModule,
-	}
-	for _, sdkKind := range defaultsSdkKinds {
-		props := defaultsProps{}
-		props.Name = proptools.StringPtr(
-			sdkKind.DefaultJavaLibraryName() + "_contributions")
-		if sdkKind == android.SdkModule {
-			props.Name = proptools.StringPtr(
-				sdkKind.DefaultJavaLibraryName() + "_contributions_full")
-		}
-		props.Api_surface = proptools.StringPtr(sdkKind.String())
-		apiSuffix := ""
-		if sdkKind != android.SdkPublic {
-			apiSuffix = "." + strings.ReplaceAll(sdkKind.String(), "-", "_")
-		}
-		props.Api_contributions = transformArray(
-			modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
-		props.Defaults_visibility = []string{"//visibility:public"}
-		props.Previous_api = proptools.StringPtr(":android.api.combined." + sdkKind.String() + ".latest")
-		ctx.CreateModule(java.DefaultsFactory, &props)
-	}
-}
-
-func createFullApiLibraries(ctx android.LoadHookContext) {
-	javaLibraryNames := []string{
-		"android_stubs_current",
-		"android_system_stubs_current",
-		"android_test_stubs_current",
-		"android_test_frameworks_core_stubs_current",
-		"android_module_lib_stubs_current",
-		"android_system_server_stubs_current",
-	}
-
-	for _, libraryName := range javaLibraryNames {
-		props := libraryProps{}
-		props.Name = proptools.StringPtr(libraryName)
-		staticLib := libraryName + ".from-source"
-		if ctx.Config().BuildFromTextStub() {
-			staticLib = libraryName + ".from-text"
-		}
-		props.Static_libs = []string{staticLib}
-		props.Defaults = []string{"android.jar_defaults"}
-		props.Visibility = []string{"//visibility:public"}
-		props.Is_stubs_module = proptools.BoolPtr(true)
-
-		ctx.CreateModule(java.LibraryFactory, &props)
-	}
-}
-
-func createFullExportableApiLibraries(ctx android.LoadHookContext) {
-	javaLibraryNames := []string{
-		"android_stubs_current_exportable",
-		"android_system_stubs_current_exportable",
-		"android_test_stubs_current_exportable",
-		"android_module_lib_stubs_current_exportable",
-		"android_system_server_stubs_current_exportable",
-	}
-
-	for _, libraryName := range javaLibraryNames {
-		props := libraryProps{}
-		props.Name = proptools.StringPtr(libraryName)
-		staticLib := libraryName + ".from-source"
-		props.Static_libs = []string{staticLib}
-		props.Defaults = []string{"android.jar_defaults"}
-		props.Visibility = []string{"//visibility:public"}
-		props.Is_stubs_module = proptools.BoolPtr(true)
-
-		ctx.CreateModule(java.LibraryFactory, &props)
-	}
-}
-
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.bootclasspath(ctx)
 	system_server_classpath := a.systemServerClasspath(ctx)
@@ -562,12 +487,6 @@
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
-
-	createApiContributionDefaults(ctx, bootclasspath)
-
-	createFullApiLibraries(ctx)
-
-	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
diff --git a/api/api_test.go b/api/api_test.go
index 47d1670..fb26f82 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -52,6 +52,12 @@
 		"core.current.stubs",
 		"ext",
 		"framework",
+		"android_stubs_current",
+		"android_system_stubs_current",
+		"android_test_stubs_current",
+		"android_test_frameworks_core_stubs_current",
+		"android_module_lib_stubs_current",
+		"android_system_server_stubs_current",
 		"android_stubs_current.from-text",
 		"android_system_stubs_current.from-text",
 		"android_test_stubs_current.from-text",
@@ -190,61 +196,60 @@
 			}
 		}),
 	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "framework-foo",
-			srcs: ["a.java"],
-			public: {
-				enabled: true,
-			},
-			system: {
-				enabled: true,
-			},
-			test: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-			api_packages: [
-				"foo",
-			],
-			sdk_version: "core_current",
-			annotations_enabled: true,
-		}
+	java_sdk_library {
+		name: "framework-foo",
+		srcs: ["a.java"],
+		public: {
+			enabled: true,
+		},
+		system: {
+			enabled: true,
+		},
+		test: {
+			enabled: true,
+		},
+		module_lib: {
+			enabled: true,
+		},
+		api_packages: [
+			"foo",
+		],
+		sdk_version: "core_current",
+		annotations_enabled: true,
+	}
+	java_sdk_library {
+		name: "framework-bar",
+		srcs: ["a.java"],
+		public: {
+			enabled: true,
+		},
+		system: {
+			enabled: true,
+		},
+		test: {
+			enabled: true,
+		},
+		module_lib: {
+			enabled: true,
+		},
+		api_packages: [
+			"foo",
+		],
+		sdk_version: "core_current",
+		annotations_enabled: true,
+	}
 
-		java_sdk_library {
-			name: "framework-bar",
-			srcs: ["a.java"],
-			public: {
-				enabled: true,
-			},
-			system: {
-				enabled: true,
-			},
-			test: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-			api_packages: [
-				"foo",
+	combined_apis {
+		name: "foo",
+		bootclasspath: [
+			"framework-bar",
+		] + select(boolean_var_for_testing(), {
+			true: [
+				"framework-foo",
 			],
-			sdk_version: "core_current",
-			annotations_enabled: true,
-		}
-
-		combined_apis {
-			name: "foo",
-			bootclasspath: [
-				"framework-bar",
-			] + select(boolean_var_for_testing(), {
-				true: [
-					"framework-foo",
-				],
-				default: [],
-			}),
-		}
+			default: [],
+		}),
+	}
 	`)
 
 	subModuleDependsOnSelectAppendedModule := java.CheckModuleHasDependency(t,
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 4a5836c..b06fb9e 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -15,11 +15,16 @@
  */
 package android.app;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
@@ -27,9 +32,12 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
 
 /**
  * A no-op implementation of {@link WallpaperManager}.
@@ -54,29 +62,19 @@
     private DisabledWallpaperManager() {
     }
 
-    @Override
-    public boolean isWallpaperSupported() {
-        return false;
+    @UnsupportedAppUsage
+    public IWallpaperManager getIWallpaperManager() {
+        return unsupported();
     }
 
     @Override
-    public boolean isSetWallpaperAllowed() {
-        return false;
+    public boolean isLockscreenLiveWallpaperEnabled() {
+        return unsupportedBoolean();
     }
 
-    private static <T> T unsupported() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
-        return null;
-    }
-
-    private static boolean unsupportedBoolean() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
-        return false;
-    }
-
-    private static int unsupportedInt() {
-        if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
-        return -1;
+    @Override
+    public boolean shouldEnableWideColorGamut() {
+        return unsupportedBoolean();
     }
 
     @Override
@@ -122,6 +120,11 @@
     }
 
     @Override
+    public boolean wallpaperSupportsWcg(int which) {
+        return unsupportedBoolean();
+    }
+
+    @Override
     public Bitmap getBitmap() {
         return unsupported();
     }
@@ -131,12 +134,61 @@
         return unsupported();
     }
 
+    @Nullable
+    public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
     @Override
     public Bitmap getBitmapAsUser(int userId, boolean hardware) {
         return unsupported();
     }
 
     @Override
+    public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
+    @Override
+    public Bitmap getBitmapAsUser(int userId, boolean hardware,
+            @SetWallpaperFlags int which, boolean returnDefault) {
+        return unsupported();
+    }
+
+    @Override
+    public Rect peekBitmapDimensions() {
+        return unsupported();
+    }
+
+    @Override
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
+        return unsupported();
+    }
+
+    @Override
+    public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+            @SetWallpaperFlags int which, boolean originalBitmap) {
+        return unsupported();
+    }
+
+    @Override
+    public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+            @Nullable Map<Point, Rect> cropHints) {
+        return unsupported();
+    }
+
+    @Override
+    public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+            @Nullable Map<Point, Rect> cropHints) {
+        return unsupported();
+    }
+
+    @Override
     public ParcelFileDescriptor getWallpaperFile(int which) {
         return unsupported();
     }
@@ -173,6 +225,17 @@
     }
 
     @Override
+    public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+            List<RectF> regions, int which) throws IllegalArgumentException {
+        unsupported();
+    }
+
+    @Override
+    public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+        unsupported();
+    }
+
+    @Override
     public ParcelFileDescriptor getWallpaperFile(int which, int userId) {
         return unsupported();
     }
@@ -192,12 +255,6 @@
         return unsupported();
     }
 
-    @Override
-    public ParcelFileDescriptor getWallpaperInfoFile() {
-        return unsupported();
-    }
-
-    @Override
     public WallpaperInfo getWallpaperInfoForUser(int userId) {
         return unsupported();
     }
@@ -213,6 +270,11 @@
     }
 
     @Override
+    public ParcelFileDescriptor getWallpaperInfoFile() {
+        return unsupported();
+    }
+
+    @Override
     public int getWallpaperId(int which) {
         return unsupportedInt();
     }
@@ -264,6 +326,11 @@
         return 0;
     }
 
+    public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
     @Override
     public void setStream(InputStream bitmapData) throws IOException {
         unsupported();
@@ -284,6 +351,19 @@
     }
 
     @Override
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
+
+    @Override
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        return unsupportedInt();
+    }
+
+    @Override
     public boolean hasResourceWallpaper(int resid) {
         return unsupportedBoolean();
     }
@@ -328,12 +408,40 @@
         return unsupportedBoolean();
     }
 
+
+    @Override
+    public void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) {
+        unsupported();
+    }
+
+    @Override
+    public @FloatRange(from = 0f, to = 1f) float getWallpaperDimAmount() {
+        return unsupportedInt();
+    }
+
+    @Override
+    public boolean lockScreenWallpaperExists() {
+        return unsupportedBoolean();
+    }
+
     @Override
     public boolean setWallpaperComponent(ComponentName name, int userId) {
         return unsupportedBoolean();
     }
 
     @Override
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which) {
+        return unsupportedBoolean();
+    }
+
+    @Override
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which, int userId) {
+        return unsupportedBoolean();
+    }
+
+    @Override
     public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
         unsupported();
     }
@@ -350,6 +458,21 @@
     }
 
     @Override
+    public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
+        unsupported();
+    }
+
+    @Override
+    public boolean isWallpaperSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isSetWallpaperAllowed() {
+        return false;
+    }
+
+    @Override
     public void clearWallpaperOffsets(IBinder windowToken) {
         unsupported();
     }
@@ -369,8 +492,18 @@
         return unsupportedBoolean();
     }
 
-    @Override
-    public boolean wallpaperSupportsWcg(int which) {
-        return unsupportedBoolean();
+    private static <T> T unsupported() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
+        return null;
+    }
+
+    private static boolean unsupportedBoolean() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
+        return false;
+    }
+
+    private static int unsupportedInt() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
+        return -1;
     }
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 1a72df1..5903a7f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -123,6 +123,8 @@
  * <p> An app can check whether wallpapers are supported for the current user, by calling
  * {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling
  * {@link #isSetWallpaperAllowed()}.
+ * Any public APIs added to WallpaperManager should have a corresponding stub in
+ * {@link DisabledWallpaperManager}.
  */
 @SystemService(Context.WALLPAPER_SERVICE)
 public class WallpaperManager {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1767d64..98e11375 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,7 @@
 import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
+import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.KeyboardLayoutSelectionResult;
@@ -239,4 +240,14 @@
     void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
 
     KeyGlyphMap getKeyGlyphMap(int deviceId);
+
+    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+    void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+
+    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+    void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
 }
diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
new file mode 100644
index 0000000..8d44917
--- /dev/null
+++ b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/** @hide */
+oneway interface IKeyboardSystemShortcutListener {
+
+    /**
+     * Called when the keyboard system shortcut is triggered.
+     */
+    void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState,
+                                           int shortcut);
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d7952eb..6bc522b 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1378,6 +1378,36 @@
     }
 
     /**
+     * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being
+     * triggered.
+     *
+     * @param executor an executor on which the callback will be called
+     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @throws IllegalArgumentException if {@code listener} has already been registered previously.
+     * @throws NullPointerException     if {@code listener} or {@code executor} is null.
+     * @hide
+     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+        mGlobal.registerKeyboardSystemShortcutListener(executor, listener);
+    }
+
+    /**
+     * Unregisters a previously added keyboard system shortcut listener.
+     *
+     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @hide
+     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void unregisterKeyboardSystemShortcutListener(
+            @NonNull KeyboardSystemShortcutListener listener) {
+        mGlobal.unregisterKeyboardSystemShortcutListener(listener);
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
@@ -1478,4 +1508,21 @@
          */
         void onStickyModifierStateChanged(@NonNull StickyModifierState state);
     }
+
+    /**
+     * A callback used to be notified about keyboard system shortcuts being triggered.
+     *
+     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @hide
+     */
+    public interface KeyboardSystemShortcutListener {
+        /**
+         * Called when a keyboard system shortcut is triggered.
+         *
+         * @param systemShortcut the shortcut info about the shortcut that was triggered.
+         */
+        void onKeyboardSystemShortcutTriggered(int deviceId,
+                @NonNull KeyboardSystemShortcut systemShortcut);
+    }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b47180..f7fa557 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -26,6 +26,7 @@
 import android.hardware.input.InputManager.InputDeviceBatteryListener;
 import android.hardware.input.InputManager.InputDeviceListener;
 import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.KeyboardSystemShortcutListener;
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
 import android.hardware.input.InputManager.StickyModifierStateListener;
 import android.hardware.lights.Light;
@@ -110,6 +111,14 @@
     @Nullable
     private IStickyModifierStateListener mStickyModifierStateListener;
 
+    private final Object mKeyboardSystemShortcutListenerLock = new Object();
+    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    @Nullable
+    private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners;
+    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    @Nullable
+    private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener;
+
     // InputDeviceSensorManager gets notified synchronously from the binder thread when input
     // devices change, so it must be synchronized with the input device listeners.
     @GuardedBy("mInputDeviceListeners")
@@ -1055,6 +1064,98 @@
         }
     }
 
+    private static final class KeyboardSystemShortcutListenerDelegate {
+        final KeyboardSystemShortcutListener mListener;
+        final Executor mExecutor;
+
+        KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener,
+                Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void onKeyboardSystemShortcutTriggered(int deviceId,
+                KeyboardSystemShortcut systemShortcut) {
+            mExecutor.execute(() ->
+                    mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut));
+        }
+    }
+
+    private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub {
+
+        @Override
+        public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes,
+                int modifierState, int shortcut) {
+            synchronized (mKeyboardSystemShortcutListenerLock) {
+                if (mKeyboardSystemShortcutListeners == null) return;
+                final int numListeners = mKeyboardSystemShortcutListeners.size();
+                for (int i = 0; i < numListeners; i++) {
+                    mKeyboardSystemShortcutListeners.get(i)
+                            .onKeyboardSystemShortcutTriggered(deviceId,
+                                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut));
+                }
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#registerKeyboardSystemShortcutListener(Executor,
+     * KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardSystemShortcutListenerLock) {
+            if (mKeyboardSystemShortcutListener == null) {
+                mKeyboardSystemShortcutListeners = new ArrayList<>();
+                mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener();
+
+                try {
+                    mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            final int numListeners = mKeyboardSystemShortcutListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) {
+                    throw new IllegalArgumentException("Listener has already been registered!");
+                }
+            }
+            KeyboardSystemShortcutListenerDelegate delegate =
+                    new KeyboardSystemShortcutListenerDelegate(listener, executor);
+            mKeyboardSystemShortcutListeners.add(delegate);
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    void unregisterKeyboardSystemShortcutListener(
+            @NonNull KeyboardSystemShortcutListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardSystemShortcutListenerLock) {
+            if (mKeyboardSystemShortcutListeners == null) {
+                return;
+            }
+            mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyboardSystemShortcutListeners.isEmpty()) {
+                try {
+                    mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mKeyboardSystemShortcutListeners = null;
+                mKeyboardSystemShortcutListener = null;
+            }
+        }
+    }
+
     /**
      * TODO(b/330517633): Cleanup the unsupported API
      */
diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java
new file mode 100644
index 0000000..89cf877
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardSystemShortcut.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the keyboard shortcut being triggered by an external keyboard.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class KeyboardSystemShortcut {
+
+    private static final String TAG = "KeyboardSystemShortcut";
+
+    @NonNull
+    private final int[] mKeycodes;
+    private final int mModifierState;
+    @SystemShortcut
+    private final int mSystemShortcut;
+
+
+    public static final int SYSTEM_SHORTCUT_UNSPECIFIED =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+    public static final int SYSTEM_SHORTCUT_HOME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
+    public static final int SYSTEM_SHORTCUT_RECENT_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
+    public static final int SYSTEM_SHORTCUT_BACK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
+    public static final int SYSTEM_SHORTCUT_APP_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
+    public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
+    public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
+    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
+    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
+    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
+    public static final int SYSTEM_SHORTCUT_VOLUME_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
+    public static final int SYSTEM_SHORTCUT_VOLUME_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
+    public static final int SYSTEM_SHORTCUT_VOLUME_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
+    public static final int SYSTEM_SHORTCUT_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
+    public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
+    public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
+    public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
+    public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
+    public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+    public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
+    public static final int SYSTEM_SHORTCUT_LOCK_SCREEN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
+    public static final int SYSTEM_SHORTCUT_OPEN_NOTES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
+    public static final int SYSTEM_SHORTCUT_TOGGLE_POWER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
+    public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
+    public static final int SYSTEM_SHORTCUT_SLEEP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
+    public static final int SYSTEM_SHORTCUT_WAKEUP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
+    public static final int SYSTEM_SHORTCUT_MEDIA_KEY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+    public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    public static final int SYSTEM_SHORTCUT_DESKTOP_MODE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
+    public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "SYSTEM_SHORTCUT_", value = {
+        SYSTEM_SHORTCUT_UNSPECIFIED,
+        SYSTEM_SHORTCUT_HOME,
+        SYSTEM_SHORTCUT_RECENT_APPS,
+        SYSTEM_SHORTCUT_BACK,
+        SYSTEM_SHORTCUT_APP_SWITCH,
+        SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+        SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+        SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+        SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+        SYSTEM_SHORTCUT_TOGGLE_TASKBAR,
+        SYSTEM_SHORTCUT_TAKE_SCREENSHOT,
+        SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+        SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+        SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+        SYSTEM_SHORTCUT_VOLUME_UP,
+        SYSTEM_SHORTCUT_VOLUME_DOWN,
+        SYSTEM_SHORTCUT_VOLUME_MUTE,
+        SYSTEM_SHORTCUT_ALL_APPS,
+        SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+        SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+        SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+        SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+        SYSTEM_SHORTCUT_SYSTEM_MUTE,
+        SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+        SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS,
+        SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT,
+        SYSTEM_SHORTCUT_LOCK_SCREEN,
+        SYSTEM_SHORTCUT_OPEN_NOTES,
+        SYSTEM_SHORTCUT_TOGGLE_POWER,
+        SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+        SYSTEM_SHORTCUT_SLEEP,
+        SYSTEM_SHORTCUT_WAKEUP,
+        SYSTEM_SHORTCUT_MEDIA_KEY,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER,
+        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS,
+        SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+        SYSTEM_SHORTCUT_DESKTOP_MODE,
+        SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface SystemShortcut {}
+
+    @DataClass.Generated.Member
+    public static String systemShortcutToString(@SystemShortcut int value) {
+        switch (value) {
+            case SYSTEM_SHORTCUT_UNSPECIFIED:
+                    return "SYSTEM_SHORTCUT_UNSPECIFIED";
+            case SYSTEM_SHORTCUT_HOME:
+                    return "SYSTEM_SHORTCUT_HOME";
+            case SYSTEM_SHORTCUT_RECENT_APPS:
+                    return "SYSTEM_SHORTCUT_RECENT_APPS";
+            case SYSTEM_SHORTCUT_BACK:
+                    return "SYSTEM_SHORTCUT_BACK";
+            case SYSTEM_SHORTCUT_APP_SWITCH:
+                    return "SYSTEM_SHORTCUT_APP_SWITCH";
+            case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT:
+                    return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT";
+            case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT:
+                    return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT";
+            case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS";
+            case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL:
+                    return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL";
+            case SYSTEM_SHORTCUT_TOGGLE_TASKBAR:
+                    return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR";
+            case SYSTEM_SHORTCUT_TAKE_SCREENSHOT:
+                    return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT";
+            case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER:
+                    return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER";
+            case SYSTEM_SHORTCUT_BRIGHTNESS_UP:
+                    return "SYSTEM_SHORTCUT_BRIGHTNESS_UP";
+            case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN:
+                    return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN";
+            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE:
+                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE";
+            case SYSTEM_SHORTCUT_VOLUME_UP:
+                    return "SYSTEM_SHORTCUT_VOLUME_UP";
+            case SYSTEM_SHORTCUT_VOLUME_DOWN:
+                    return "SYSTEM_SHORTCUT_VOLUME_DOWN";
+            case SYSTEM_SHORTCUT_VOLUME_MUTE:
+                    return "SYSTEM_SHORTCUT_VOLUME_MUTE";
+            case SYSTEM_SHORTCUT_ALL_APPS:
+                    return "SYSTEM_SHORTCUT_ALL_APPS";
+            case SYSTEM_SHORTCUT_LAUNCH_SEARCH:
+                    return "SYSTEM_SHORTCUT_LAUNCH_SEARCH";
+            case SYSTEM_SHORTCUT_LANGUAGE_SWITCH:
+                    return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH";
+            case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS:
+                    return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS";
+            case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK:
+                    return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK";
+            case SYSTEM_SHORTCUT_SYSTEM_MUTE:
+                    return "SYSTEM_SHORTCUT_SYSTEM_MUTE";
+            case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION";
+            case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS:
+                    return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS";
+            case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT:
+                    return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT";
+            case SYSTEM_SHORTCUT_LOCK_SCREEN:
+                    return "SYSTEM_SHORTCUT_LOCK_SCREEN";
+            case SYSTEM_SHORTCUT_OPEN_NOTES:
+                    return "SYSTEM_SHORTCUT_OPEN_NOTES";
+            case SYSTEM_SHORTCUT_TOGGLE_POWER:
+                    return "SYSTEM_SHORTCUT_TOGGLE_POWER";
+            case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION";
+            case SYSTEM_SHORTCUT_SLEEP:
+                    return "SYSTEM_SHORTCUT_SLEEP";
+            case SYSTEM_SHORTCUT_WAKEUP:
+                    return "SYSTEM_SHORTCUT_WAKEUP";
+            case SYSTEM_SHORTCUT_MEDIA_KEY:
+                    return "SYSTEM_SHORTCUT_MEDIA_KEY";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER";
+            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS:
+                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS";
+            case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+                    return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+            case SYSTEM_SHORTCUT_DESKTOP_MODE:
+                    return "SYSTEM_SHORTCUT_DESKTOP_MODE";
+            case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION:
+                    return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyboardSystemShortcut(
+            @NonNull int[] keycodes,
+            int modifierState,
+            @SystemShortcut int systemShortcut) {
+        this.mKeycodes = keycodes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mKeycodes);
+        this.mModifierState = modifierState;
+        this.mSystemShortcut = systemShortcut;
+
+        if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE)
+                && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) {
+            throw new java.lang.IllegalArgumentException(
+                    "systemShortcut was " + mSystemShortcut + " but must be one of: "
+                            + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), "
+                            + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), "
+                            + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), "
+                            + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), "
+                            + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), "
+                            + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), "
+                            + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), "
+                            + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), "
+                            + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), "
+                            + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), "
+                            + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), "
+                            + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), "
+                            + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), "
+                            + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), "
+                            + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), "
+                            + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), "
+                            + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), "
+                            + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), "
+                            + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), "
+                            + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), "
+                            + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), "
+                            + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), "
+                            + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), "
+                            + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), "
+                            + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
+                            + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), "
+                            + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull int[] getKeycodes() {
+        return mKeycodes;
+    }
+
+    @DataClass.Generated.Member
+    public int getModifierState() {
+        return mModifierState;
+    }
+
+    @DataClass.Generated.Member
+    public @SystemShortcut int getSystemShortcut() {
+        return mSystemShortcut;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyboardSystemShortcut { " +
+                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
+                "modifierState = " + mModifierState + ", " +
+                "systemShortcut = " + systemShortcutToString(mSystemShortcut) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyboardSystemShortcut that = (KeyboardSystemShortcut) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
+                && mModifierState == that.mModifierState
+                && mSystemShortcut == that.mSystemShortcut;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
+        _hash = 31 * _hash + mModifierState;
+        _hash = 31 * _hash + mSystemShortcut;
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1722890917041L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java",
+            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final  int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final  int SYSTEM_SHORTCUT_HOME\npublic static final  int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final  int SYSTEM_SHORTCUT_BACK\npublic static final  int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final  int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final  int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final  int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final  int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final  int SYSTEM_SHORTCUT_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final  int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final  int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final  int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final  int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final  int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_SLEEP\npublic static final  int SYSTEM_SHORTCUT_WAKEUP\npublic static final  int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final  int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e79b8f3..de39847 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -524,19 +524,12 @@
 
     /**
      * @hide
-     * The IME is active and ready with views but set invisible.
-     * This flag cannot be combined with {@link #IME_VISIBLE}.
-     */
-    public static final int IME_INVISIBLE = 0x4;
-
-    /**
-     * @hide
      * The IME is visible, but not yet perceptible to the user (e.g. fading in)
      * by {@link android.view.WindowInsetsController}.
      *
      * @see InputMethodManager#reportPerceptible
      */
-    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8;
+    public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4;
 
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
@@ -3125,7 +3118,7 @@
         mInShowWindow = true;
         final int previousImeWindowStatus =
                 (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
-                        ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+                        ? (!mWindowVisible ? -1 : IME_VISIBLE) : 0);
         startViews(prepareWindow(showInput));
         final int nextImeWindowStatus = mapToImeWindowStatus();
         if (previousImeWindowStatus != nextImeWindowStatus) {
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 97993b6..6aa9852 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -42,4 +42,12 @@
     // vibrate/isVibrating/cancel.
     oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
             String reason, int flags, int privFlags);
+
+    // Similar to performHapticFeedback but the effect is customized to the input device. The
+    // customization for each constant is defined on a device basis, and the behavior will be the
+    // same as performHapticFeedback when no customization is provided for a given constant and
+    // device.
+    oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+            int constant, int inputDeviceId, int inputSource, String reason, int flags,
+            int privFlags);
 }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 5339d73..011a3ee 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -215,6 +215,17 @@
     }
 
     @Override
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, int flags, int privFlags) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device; no vibrator manager.");
+            return;
+        }
+        mVibratorManager.performHapticFeedbackForInputDevice(constant, inputDeviceId, inputSource,
+                reason, flags, privFlags);
+    }
+
+    @Override
     public void cancel() {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index a9846ba..58ab5b6 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -161,6 +161,22 @@
     }
 
     @Override
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, int flags, int privFlags) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device;"
+                            + " no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName,
+                    constant, inputDeviceId, inputSource, reason, flags, privFlags);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to perform haptic feedback for input device.", e);
+        }
+    }
+
+    @Override
     public void cancel() {
         cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
     }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 161cce0..36233b7 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -553,6 +553,31 @@
     }
 
     /**
+     * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take into the
+     * consideration the {@link InputDevice} that triggered the haptic
+     *
+     * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+     * the {@code constant}, which should be one of the effect constants provided in
+     * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+     * consistent across all usages on the same device.
+     *
+     * @param constant      the ID for the haptic feedback. This should be one of the constants
+     *                      defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId the integer id of the input device that triggered the haptic feedback.
+     * @param inputSource   the {@link InputDevice.Source} that triggered the haptic feedback.
+     * @param reason        the reason for this haptic feedback.
+     * @param flags         Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags     Additional private flags as per {@link HapticFeedbackConstants}.
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(
+            int constant, int inputDeviceId, int inputSource, String reason,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+    }
+
+    /**
      * Query whether the vibrator natively supports the given effects.
      *
      * <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 2c7a852..0428876 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -155,6 +155,27 @@
     }
 
     /**
+     * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take input
+     * into consideration.
+     *
+     * @param constant      the ID of the requested haptic feedback. Should be one of the constants
+     *                      defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId the integer id of the input device that customizes the haptic feedback
+     *                      corresponding to the {@code constant}.
+     * @param inputSource   the {@link InputDevice.Source} that customizes the haptic feedback
+     *                      corresponding to the {@code constant}.
+     * @param reason        the reason for this haptic feedback.
+     * @param flags         Additional flags as per {@link HapticFeedbackConstants}.
+     * @param privFlags     Additional private flags as per {@link HapticFeedbackConstants}.
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+            int inputSource, String reason, @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+    }
+
+    /**
      * Turn all the vibrators off.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 3fad8dd..1a19bb2 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -3,13 +3,6 @@
 
 flag {
     namespace: "haptics"
-    name: "use_vibrator_haptic_feedback"
-    description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
-    bug: "295459081"
-}
-
-flag {
-    namespace: "haptics"
     name: "haptic_feedback_vibration_oem_customization_enabled"
     description: "Enables OEMs/devices to customize vibrations for haptic feedback"
     # Make read only. This is because the flag is used only once, and this could happen before
@@ -88,3 +81,25 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "load_haptic_feedback_vibration_customization_from_resources"
+    description: "Load haptic feedback vibrations customization from resources."
+    is_fixed_read_only: true
+    bug: "295142743"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
+
+flag {
+    namespace: "haptics"
+    name: "haptic_feedback_input_source_customization_enabled"
+    description: "Enabled the extended haptic feedback customization by input source."
+    bug: "331819348"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5703f69..791b306 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12543,6 +12543,19 @@
                 "launcher_taskbar_education_showing";
 
         /**
+         * Whether any Compat UI Education is currently showing.
+         *
+         * <p>1 if true, 0 or unset otherwise.
+         *
+         * <p>This setting is used to inform other components that the Compat UI Education is
+         * currently showing, which can prevent them from showing something else to the user.
+         *
+         * @hide
+         */
+        public static final String COMPAT_UI_EDUCATION_SHOWING =
+                "compat_ui_education_showing";
+
+        /**
          * Whether or not adaptive charging feature is enabled by user.
          * Type: int (0 for false, 1 for true)
          * Default: 1
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 5e15e01..224379b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -25,6 +25,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
 import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
 import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
@@ -454,7 +456,7 @@
             newRule.conditionId = Uri.EMPTY;
             newRule.allowManualInvocation = true;
             newRule.zenPolicy = getDefaultZenPolicy();
-            newRule.pkg = "android";
+            newRule.pkg = PACKAGE_ANDROID;
             manualRule = newRule;
         }
     }
@@ -957,15 +959,9 @@
         rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
         boolean readSuppressedEffects = false;
         boolean readManualRule = false;
+        boolean readManualRuleWithoutPolicy = false;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
-            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
-                if (Flags.modesUi() && !readManualRule) {
-                    // migrate from fields on config into manual rule
-                    rt.manualRule.zenPolicy = rt.toZenPolicy();
-                }
-                return rt;
-            }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
                     rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
@@ -1034,9 +1030,17 @@
                     rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                 } else if (MANUAL_TAG.equals(tag)) {
-                    rt.manualRule = readRuleXml(parser);
-                    if (rt.manualRule != null) {
+                    ZenRule manualRule = readRuleXml(parser);
+                    if (manualRule != null) {
+                        rt.manualRule = manualRule;
+
+                        // Manual rule may be present prior to modes_ui if it were on, but in that
+                        // case it would not have a set policy, so make note of the need to set
+                        // it up later.
                         readManualRule = true;
+                        if (rt.manualRule.zenPolicy == null) {
+                            readManualRuleWithoutPolicy = true;
+                        }
                     }
                 } else if (AUTOMATIC_TAG.equals(tag)
                         || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
@@ -1058,6 +1062,23 @@
                             STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
                 }
             }
+            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+                if (Flags.modesUi() && (!readManualRule || readManualRuleWithoutPolicy)) {
+                    // migrate from fields on config into manual rule
+                    rt.manualRule.zenPolicy = rt.toZenPolicy();
+                    if (readManualRuleWithoutPolicy) {
+                        // indicates that the xml represents a pre-modes_ui XML with an enabled
+                        // manual rule; set rule active, and fill in other fields as would be done
+                        // in ensureManualZenRule() and setManualZenMode().
+                        rt.manualRule.pkg = PACKAGE_ANDROID;
+                        rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
+                        rt.manualRule.condition = new Condition(
+                                rt.manualRule.conditionId != null ? rt.manualRule.conditionId
+                                        : Uri.EMPTY, "", STATE_TRUE);
+                    }
+                }
+                return rt;
+            }
         }
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 88a1b9c..1b85191 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -118,6 +118,13 @@
 }
 
 flag {
+  name: "insert_mode_highlight_range"
+  namespace: "text"
+  description: "Make the highlight range stick after editing, this handles the corner cases where the entire text is replaced with itself(or transformed by developer) after each editing."
+  bug: "355137282"
+}
+
+flag {
   name: "insert_mode_not_update_selection"
   namespace: "text"
   description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
index 59b80f3..6c6576f 100644
--- a/core/java/android/text/method/InsertModeTransformationMethod.java
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
 
 import java.lang.reflect.Array;
 
@@ -171,9 +172,15 @@
                 // The text change is before the highlight start, move the highlight start.
                 mStart += diff;
             } else {
-                // The text change covers the highlight start. Extend the highlight start to the
-                // change start. This should be a rare case.
-                mStart = start;
+                if (Flags.insertModeHighlightRange()) {
+                    // The text change covers the highlight start. Don't change the start except
+                    // when it's out of range.
+                    mStart = Math.min(mStart, s.length());
+                } else {
+                    // The text change covers the highlight start. Extend the highlight start to the
+                    // change start. This should be a rare case.
+                    mStart = start;
+                }
             }
         }
 
@@ -181,9 +188,15 @@
             // The text change is before the highlight end, move the highlight end.
             mEnd += diff;
         } else if (start < mEnd) {
-            // The text change covers the highlight end. Extend the highlight end to the
-            // change end. This should be a rare case.
-            mEnd = start + count;
+            if (Flags.insertModeHighlightRange()) {
+                // The text change covers the highlight end. Don't change the end except when it's
+                // out of range.
+                mEnd = Math.min(mEnd, s.length());
+            } else {
+                // The text change covers the highlight end. Extend the highlight end to the
+                // change end. This should be a rare case.
+                mEnd = start + count;
+            }
         }
     }
 
diff --git a/core/java/android/text/style/AccessibilityClickableSpan.java b/core/java/android/text/style/AccessibilityClickableSpan.java
index 534ce63..ee8d156 100644
--- a/core/java/android/text/style/AccessibilityClickableSpan.java
+++ b/core/java/android/text/style/AccessibilityClickableSpan.java
@@ -156,4 +156,12 @@
                     return new AccessibilityClickableSpan[size];
                 }
             };
+
+    /**
+     * @return the ID of the original clickable span that this is applied to.
+     * @hide
+     */
+    public int getOriginalClickableSpanId() {
+        return mOriginalClickableSpanId;
+    }
 }
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index b3e7bda..f70e6c5 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -119,7 +119,10 @@
         this(gapWidth, color, true, bulletRadius);
     }
 
-    private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
+    /**
+     * @hide
+     */
+    public BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
             @IntRange(from = 0) int bulletRadius) {
         mGapWidth = gapWidth;
         mBulletRadius = bulletRadius;
@@ -199,6 +202,14 @@
         return mColor;
     }
 
+    /**
+     * @return true if the bullet should apply the color.
+     * @hide
+     */
+    public boolean getWantColor() {
+        return mWantColor;
+    }
+
     @Override
     public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir,
             int top, int baseline, int bottom,
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index ad044af..0cf96f6 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -248,21 +248,42 @@
     }
 
     public SuggestionSpan(Parcel src) {
-        mSuggestions = src.readStringArray();
-        mFlags = src.readInt();
-        mLocaleStringForCompatibility = src.readString();
-        mLanguageTag = src.readString();
-        mHashCode = src.readInt();
-        mEasyCorrectUnderlineColor = src.readInt();
-        mEasyCorrectUnderlineThickness = src.readFloat();
-        mMisspelledUnderlineColor = src.readInt();
-        mMisspelledUnderlineThickness = src.readFloat();
-        mAutoCorrectionUnderlineColor = src.readInt();
-        mAutoCorrectionUnderlineThickness = src.readFloat();
-        mGrammarErrorUnderlineColor = src.readInt();
-        mGrammarErrorUnderlineThickness = src.readFloat();
+        this(/* suggestions= */ src.readStringArray(), /* flags= */ src.readInt(),
+                /* localStringForCompatibility= */ src.readString(),
+                /* languageTag= */ src.readString(), /* hashCode= */ src.readInt(),
+                /* easyCorrectUnderlineColor= */ src.readInt(),
+                /* easyCorrectUnderlineThickness= */ src.readFloat(),
+                /* misspelledUnderlineColor= */ src.readInt(),
+                /* misspelledUnderlineThickness= */ src.readFloat(),
+                /* autoCorrectionUnderlineColor= */ src.readInt(),
+                /* autoCorrectionUnderlineThickness= */ src.readFloat(),
+                /* grammarErrorUnderlineColor= */ src.readInt(),
+                /* grammarErrorUnderlineThickness= */ src.readFloat());
     }
 
+    /** @hide */
+    public SuggestionSpan(String[] suggestions, int flags, String localeStringForCompatibility,
+            String languageTag, int hashCode, int easyCorrectUnderlineColor,
+            float easyCorrectUnderlineThickness, int misspelledUnderlineColor,
+            float misspelledUnderlineThickness, int autoCorrectionUnderlineColor,
+            float autoCorrectionUnderlineThickness, int grammarErrorUnderlineColor,
+            float grammarErrorUnderlineThickness) {
+        mSuggestions = suggestions;
+        mFlags = flags;
+        mLocaleStringForCompatibility = localeStringForCompatibility;
+        mLanguageTag = languageTag;
+        mHashCode = hashCode;
+        mEasyCorrectUnderlineColor = easyCorrectUnderlineColor;
+        mEasyCorrectUnderlineThickness = easyCorrectUnderlineThickness;
+        mMisspelledUnderlineColor = misspelledUnderlineColor;
+        mMisspelledUnderlineThickness = misspelledUnderlineThickness;
+        mAutoCorrectionUnderlineColor = autoCorrectionUnderlineColor;
+        mAutoCorrectionUnderlineThickness = autoCorrectionUnderlineThickness;
+        mGrammarErrorUnderlineColor = grammarErrorUnderlineColor;
+        mGrammarErrorUnderlineThickness = grammarErrorUnderlineThickness;
+    }
+
+
     /**
      * @return an array of suggestion texts for this span
      */
@@ -452,4 +473,44 @@
     public void notifySelection(Context context, String original, int index) {
         Log.w(TAG, "notifySelection() is deprecated.  Does nothing.");
     }
+
+    /** @hide */
+    public float getEasyCorrectUnderlineThickness() {
+        return mEasyCorrectUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getEasyCorrectUnderlineColor() {
+        return mEasyCorrectUnderlineColor;
+    }
+
+    /** @hide */
+    public float getMisspelledUnderlineThickness() {
+        return mMisspelledUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getMisspelledUnderlineColor() {
+        return mMisspelledUnderlineColor;
+    }
+
+    /** @hide */
+    public float getAutoCorrectionUnderlineThickness() {
+        return mAutoCorrectionUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getAutoCorrectionUnderlineColor() {
+        return mAutoCorrectionUnderlineColor;
+    }
+
+    /** @hide */
+    public float getGrammarErrorUnderlineThickness() {
+        return mGrammarErrorUnderlineThickness;
+    }
+
+    /** @hide */
+    public int getGrammarErrorUnderlineColor() {
+        return mGrammarErrorUnderlineColor;
+    }
 }
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index d61228b..245a9db 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -233,36 +233,59 @@
     }
 
     public TextAppearanceSpan(Parcel src) {
-        mFamilyName = src.readString();
-        mStyle = src.readInt();
-        mTextSize = src.readInt();
-        if (src.readInt() != 0) {
-            mTextColor = ColorStateList.CREATOR.createFromParcel(src);
-        } else {
-            mTextColor = null;
-        }
-        if (src.readInt() != 0) {
-            mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
-        } else {
-            mTextColorLink = null;
-        }
-        mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
+        this(/* familyName= */ src.readString(),
+                /* style= */ src.readInt(),
+                /* textSize= */ src.readInt(),
+                /* textColor= */ (src.readInt() != 0)
+                        ? ColorStateList.CREATOR.createFromParcel(src) : null,
+                /* textColorLink= */ (src.readInt() != 0)
+                        ? ColorStateList.CREATOR.createFromParcel(src) : null,
+                /* typeface= */ LeakyTypefaceStorage.readTypefaceFromParcel(src),
+                /* textFontWeight= */ src.readInt(),
+                /* textLocales= */
+                src.readParcelable(LocaleList.class.getClassLoader(), LocaleList.class),
+                /* shadowRadius= */ src.readFloat(),
+                /* shadowDx= */ src.readFloat(),
+                /* shadowDy= */ src.readFloat(),
+                /* shadowColor= */ src.readInt(),
+                /* hasElegantTextHeight= */ src.readBoolean(),
+                /* elegantTextHeight= */ src.readBoolean(),
+                /* hasLetterSpacing= */ src.readBoolean(),
+                /* letterSpacing= */ src.readFloat(),
+                /* fontFeatureSettings= */ src.readString(),
+                /* fontVariationSettings= */ src.readString());
+    }
 
-        mTextFontWeight = src.readInt();
-        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
+    /** @hide */
+    public TextAppearanceSpan(@Nullable String familyName, int style, int textSize,
+            @Nullable ColorStateList textColor, @Nullable ColorStateList textColorLink,
+            @Nullable Typeface typeface,
+            int textFontWeight, @Nullable LocaleList textLocales, float shadowRadius,
+            float shadowDx, float shadowDy, int shadowColor, boolean hasElegantTextHeight,
+            boolean elegantTextHeight, boolean hasLetterSpacing, float letterSpacing,
+            @Nullable String fontFeatureSettings, @Nullable String fontVariationSettings) {
+        mFamilyName = familyName;
+        mStyle = style;
+        mTextSize = textSize;
+        mTextColor = textColor;
+        mTextColorLink = textColorLink;
+        mTypeface = typeface;
 
-        mShadowRadius = src.readFloat();
-        mShadowDx = src.readFloat();
-        mShadowDy = src.readFloat();
-        mShadowColor = src.readInt();
+        mTextFontWeight = textFontWeight;
+        mTextLocales = textLocales;
 
-        mHasElegantTextHeight = src.readBoolean();
-        mElegantTextHeight = src.readBoolean();
-        mHasLetterSpacing = src.readBoolean();
-        mLetterSpacing = src.readFloat();
+        mShadowRadius = shadowRadius;
+        mShadowDx = shadowDx;
+        mShadowDy = shadowDy;
+        mShadowColor = shadowColor;
 
-        mFontFeatureSettings = src.readString();
-        mFontVariationSettings = src.readString();
+        mHasElegantTextHeight = hasElegantTextHeight;
+        mElegantTextHeight = elegantTextHeight;
+        mHasLetterSpacing = hasLetterSpacing;
+        mLetterSpacing = letterSpacing;
+
+        mFontFeatureSettings = fontFeatureSettings;
+        mFontVariationSettings = fontVariationSettings;
     }
 
     public int getSpanTypeId() {
@@ -564,4 +587,14 @@
                 + ", fontVariationSettings='" + getFontVariationSettings() + '\''
                 + '}';
     }
+
+    /** @hide */
+    public boolean hasElegantTextHeight() {
+        return mHasElegantTextHeight;
+    }
+
+    /** @hide */
+    public boolean hasLetterSpacing() {
+        return mHasLetterSpacing;
+    }
 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 12dbc5a..157cec8 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -708,7 +708,7 @@
      */
     @Nullable
     public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
-        Display.Mode[] modes = supportedModes;
+        Display.Mode[] modes = appsSupportedModes;
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
             if (modes[i].matches(
@@ -723,7 +723,7 @@
      * Returns the list of supported refresh rates in the default mode.
      */
     public float[] getDefaultRefreshRates() {
-        Display.Mode[] modes = supportedModes;
+        Display.Mode[] modes = appsSupportedModes;
         ArraySet<Float> rates = new ArraySet<>();
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index f370256..0001176 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -100,8 +100,12 @@
 
         if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) {
             mTotalScrollPixels %= mTickIntervalPixels;
-            // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+            if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+                mView.performHapticFeedbackForInputDevice(
+                        HapticFeedbackConstants.SCROLL_TICK, inputDeviceId, source, /* flags= */ 0);
+            } else {
+                mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+            }
         }
     }
 
@@ -115,9 +119,12 @@
         if (!mCanPlayLimitFeedback) {
             return;
         }
-
-        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+        if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+            mView.performHapticFeedbackForInputDevice(
+                    HapticFeedbackConstants.SCROLL_LIMIT, inputDeviceId, source, /* flags= */ 0);
+        } else {
+            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+        }
 
         mCanPlayLimitFeedback = false;
     }
@@ -128,8 +135,13 @@
         if (!mHapticScrollFeedbackEnabled) {
             return;
         }
-        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
-        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+        if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+            mView.performHapticFeedbackForInputDevice(
+                    HapticFeedbackConstants.SCROLL_ITEM_FOCUS, inputDeviceId, source,
+                    /* flags= */ 0);
+        } else {
+            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+        }
         mCanPlayLimitFeedback = true;
     }
 
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 762a302..11a3168 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,15 +140,6 @@
     oneway void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction,
             int seqId);
 
-    @UnsupportedAppUsage
-    boolean performHapticFeedback(int effectId, int flags, int privFlags);
-
-    /**
-     * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
-     * permission.
-     */
-    oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
-
     /**
      * Initiate the drag operation itself
      *
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d83f344..7896cbd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -685,9 +685,6 @@
      */
     private @InsetsType int mCancelledForNewAnimationTypes;
 
-    private final Runnable mInvokeControllableInsetsChangedListeners =
-            this::invokeControllableInsetsChangedListeners;
-
     private final InsetsState.OnTraverseCallbacks mRemoveGoneSources =
             new InsetsState.OnTraverseCallbacks() {
 
@@ -2206,7 +2203,6 @@
      * @return The types that are now animating due to a listener invoking control/show/hide
      */
     private @InsetsType int invokeControllableInsetsChangedListeners() {
-        mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
         mLastStartedAnimTypes = 0;
         @InsetsType int types = calculateControllableTypes();
         int size = mControllableInsetsChangedListeners.size();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5f8bea1..dbd65de 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -142,7 +142,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.Vibrator;
-import android.os.vibrator.Flags;
 import android.service.credentials.CredentialProviderService;
 import android.sysprop.DisplayProperties;
 import android.text.InputType;
@@ -5712,9 +5711,6 @@
      */
     private PointerIcon mMousePointerIcon;
 
-    /** Vibrator for haptic feedback. */
-    private Vibrator mVibrator;
-
     /**
      * @hide
      */
@@ -28667,37 +28663,63 @@
      * @param flags Additional flags as per {@link HapticFeedbackConstants}.
      */
     public boolean performHapticFeedback(int feedbackConstant, int flags) {
-        if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
-                || mAttachInfo == null) {
+        if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
             return false;
         }
+
+        int privFlags = computeHapticFeedbackPrivateFlags();
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
+    }
+
+    /**
+     * <p>Provide haptic feedback to the user for this view.
+     *
+     * <p>Call this method (vs {@link #performHapticFeedback(int)}) to specify more details about
+     * the {@link InputDevice} that caused this haptic feedback. The framework will choose and
+     * provide a haptic feedback based on these details.
+     *
+     * <p>The feedback will only be performed if {@link #isHapticFeedbackEnabled()} is {@code true}.
+     *
+     * @param feedbackConstant One of the constants defined in {@link HapticFeedbackConstants}.
+     * @param inputDeviceId The ID of the {@link InputDevice} that generated the event which
+     *          triggered this haptic feedback request.
+     * @param inputSource The input source of the event which triggered this haptic feedback
+     *          request, defined as {@code InputDevice#SOURCE_*}.
+     *
+     * @hide
+     */
+    public void performHapticFeedbackForInputDevice(int feedbackConstant, int inputDeviceId,
+            int inputSource, int flags) {
+        if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
+            return;
+        }
+
+        int privFlags = computeHapticFeedbackPrivateFlags();
+        mAttachInfo.mRootCallbacks.performHapticFeedbackForInputDevice(
+                feedbackConstant, inputDeviceId, inputSource, flags, privFlags);
+    }
+
+    private boolean isPerformHapticFeedbackSuppressed(int feedbackConstant, int flags) {
+        if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
+                || mAttachInfo == null
+                || mAttachInfo.mSession == null) {
+            return true;
+        }
         //noinspection SimplifiableIfStatement
         if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
                 && !isHapticFeedbackEnabled()) {
-            return false;
+            return true;
         }
+        return false;
+    }
 
+    private int computeHapticFeedbackPrivateFlags() {
         int privFlags = 0;
         if (mAttachInfo.mViewRootImpl != null
                 && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
             privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
         }
-        if (Flags.useVibratorHapticFeedback()) {
-            if (!mAttachInfo.canPerformHapticFeedback()) {
-                return false;
-            }
-            getSystemVibrator().performHapticFeedback(feedbackConstant,
-                    "View#performHapticFeedback", flags, privFlags);
-            return true;
-        }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
-    }
-
-    private Vibrator getSystemVibrator() {
-        if (mVibrator != null) {
-            return mVibrator;
-        }
-        return mVibrator = mContext.getSystemService(Vibrator.class);
+        return privFlags;
     }
 
     /**
@@ -31731,6 +31753,11 @@
             boolean performHapticFeedback(int effectId,
                     @HapticFeedbackConstants.Flags int flags,
                     @HapticFeedbackConstants.PrivateFlags int privFlags);
+
+            void performHapticFeedbackForInputDevice(int effectId,
+                    int inputDeviceId, int inputSource,
+                    @HapticFeedbackConstants.Flags int flags,
+                    @HapticFeedbackConstants.PrivateFlags int privFlags);
         }
 
         /**
@@ -32297,11 +32324,6 @@
             return events;
         }
 
-        private boolean canPerformHapticFeedback() {
-            return mSession != null
-                    && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
-        }
-
         @Nullable
         ScrollCaptureInternal getScrollCaptureInternal() {
             if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9518abf..b8a885e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -207,6 +207,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.sysprop.DisplayProperties;
 import android.sysprop.ViewProperties;
@@ -362,14 +363,6 @@
     private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
 
     /**
-     * Controls whether to use the new oneway performHapticFeedback call. This returns
-     * true in a few more conditions, but doesn't affect which haptics happen. Notably, it
-     * makes the call to performHapticFeedback non-blocking, which reduces potential UI jank.
-     * This is intended as a temporary flag, ultimately becoming permanently 'true'.
-     */
-    private static final boolean USE_ASYNC_PERFORM_HAPTIC_FEEDBACK = true;
-
-    /**
      * Whether the client (system UI) is handling the transient gesture and the corresponding
      * animation.
      * @hide
@@ -956,6 +949,11 @@
      */
     AudioManager mAudioManager;
 
+    /**
+     * see {@link #performHapticFeedback(int, int, int)}
+     */
+    Vibrator mVibrator;
+
     final AccessibilityManager mAccessibilityManager;
 
     AccessibilityInteractionController mAccessibilityInteractionController;
@@ -9236,6 +9234,13 @@
         return mAudioManager;
     }
 
+    private Vibrator getSystemVibrator() {
+        if (mVibrator == null) {
+            mVibrator = mContext.getSystemService(Vibrator.class);
+        }
+        return mVibrator;
+    }
+
     private @Nullable AutofillManager getAutofillManager() {
         if (mView instanceof ViewGroup) {
             ViewGroup decorView = (ViewGroup) mView;
@@ -9662,17 +9667,23 @@
             return false;
         }
 
-        try {
-            if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
-                mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
-                return true;
-            } else {
-                // Original blocking binder call path.
-                return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
-            }
-        } catch (RemoteException e) {
-            return false;
+        getSystemVibrator().performHapticFeedback(
+                effectId, "ViewRootImpl#performHapticFeedback", flags, privFlags);
+        return true;
+    }
+
+    @Override
+    public void performHapticFeedbackForInputDevice(int effectId,
+            int inputDeviceId, int inputSource,
+            @HapticFeedbackConstants.Flags int flags,
+            @HapticFeedbackConstants.PrivateFlags int privFlags) {
+        if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+            return;
         }
+
+        getSystemVibrator().performHapticFeedbackForInputDevice(effectId,
+                inputDeviceId, inputSource, "ViewRootImpl#performHapticFeedbackForInputDevice",
+                flags, privFlags);
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 85d4ec0..017e004 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -132,6 +132,7 @@
 import android.window.TaskFpsCallback;
 import android.window.TrustedPresentationThresholds;
 
+import com.android.internal.R;
 import com.android.window.flags.Flags;
 
 import java.lang.annotation.ElementType;
@@ -482,6 +483,11 @@
      * @hide
      */
     int TRANSIT_PREPARE_BACK_NAVIGATION = 13;
+    /**
+     * An Activity was going to be invisible from back navigation.
+     * @hide
+     */
+    int TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION = 14;
 
     /**
      * The first slot for custom transition types. Callers (like Shell) can make use of custom
@@ -512,6 +518,7 @@
             TRANSIT_WAKE,
             TRANSIT_SLEEP,
             TRANSIT_PREPARE_BACK_NAVIGATION,
+            TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION,
             TRANSIT_FIRST_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -1926,6 +1933,7 @@
             case TRANSIT_WAKE: return "WAKE";
             case TRANSIT_SLEEP: return "SLEEP";
             case TRANSIT_PREPARE_BACK_NAVIGATION: return "PREDICTIVE_BACK";
+            case TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION: return "CLOSE_PREDICTIVE_BACK";
             case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
             default:
                 if (type > TRANSIT_FIRST_CUSTOM) {
@@ -3468,6 +3476,13 @@
         public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
 
         /**
+         * Flag to indicate that the window has the
+         * {@link R.styleable.Window_windowOptOutEdgeToEdgeEnforcement} flag set.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE = 1 << 26;
+
+        /**
          * Flag to indicate that the window is controlling how it fits window insets on its own.
          * So we don't need to adjust its attributes for fitting window insets.
          * @hide
@@ -3540,6 +3555,7 @@
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                 PRIVATE_FLAG_CONSUME_IME_INSETS,
+                PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
                 PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                 PRIVATE_FLAG_TRUSTED_OVERLAY,
                 PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
@@ -3644,6 +3660,10 @@
                         equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
                         name = "CONSUME_IME_INSETS"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+                        equals = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+                        name = "OPTOUT_EDGE_TO_EDGE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                         equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                         name = "FIT_INSETS_CONTROLLED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 961a9c4..f50ea91 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -17,6 +17,8 @@
 package android.view;
 
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import android.animation.ValueAnimator;
@@ -26,6 +28,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.graphics.HardwareRenderer;
 import android.os.Binder;
 import android.os.Build;
@@ -45,6 +48,7 @@
 import android.window.InputTransferToken;
 import android.window.TrustedPresentationThresholds;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FastPrintWriter;
 
@@ -356,12 +360,12 @@
         }
 
         final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+        final Context context = view.getContext();
         if (parentWindow != null) {
             parentWindow.adjustLayoutParamsForSubWindow(wparams);
         } else {
             // If there's no parent, then hardware acceleration for this view is
             // set from the application's hardware acceleration setting.
-            final Context context = view.getContext();
             if (context != null
                     && (context.getApplicationInfo().flags
                     & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
@@ -369,6 +373,14 @@
             }
         }
 
+        if (context != null && wparams.type > LAST_APPLICATION_WINDOW) {
+            final TypedArray styles = context.obtainStyledAttributes(R.styleable.Window);
+            if (styles.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) {
+                wparams.privateFlags |= PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
+            }
+            styles.recycle();
+        }
+
         ViewRootImpl root;
         View panelParentView = null;
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0d027f1..d2747e4 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -504,16 +504,6 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
-        return false;
-    }
-
-    @Override
-    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
-        performHapticFeedback(effectId, flags, privFlags);
-    }
-
-    @Override
     public android.os.IBinder performDrag(android.view.IWindow window, int flags,
             android.view.SurfaceControl surface, int touchSource, int touchDeviceId,
             int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
index 600fea4..080f22e 100644
--- a/core/java/android/widget/RemoteViewsSerializers.java
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -15,12 +15,55 @@
  */
 package android.widget;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.noBreakNoHyphenationSpan;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.FlaggedApi;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BlendMode;
 import android.graphics.drawable.Icon;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.PersistableBundle;
+import android.text.Annotation;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityReplacementSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.EasyEditSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.LineBreakConfigSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.LocaleSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.SpellCheckSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionRangeSpan;
+import android.text.style.SuggestionSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TtsSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.proto.ProtoInputStream;
@@ -29,7 +72,11 @@
 
 import androidx.annotation.NonNull;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Function;
 
 /**
@@ -59,12 +106,13 @@
                 break;
             case Icon.TYPE_ADAPTIVE_BITMAP:
                 final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
-                icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
-                        adaptiveBitmapBytes);
+                icon.getBitmap()
+                        .compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, adaptiveBitmapBytes);
                 out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
                 break;
             case Icon.TYPE_RESOURCE:
-                out.write(RemoteViewsProto.Icon.RESOURCE,
+                out.write(
+                        RemoteViewsProto.Icon.RESOURCE,
                         appResources.getResourceName(icon.getResId()));
                 break;
             case Icon.TYPE_DATA:
@@ -91,7 +139,8 @@
         while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
             switch (in.getFieldNumber()) {
                 case (int) RemoteViewsProto.Icon.BLEND_MODE:
-                    values.put(RemoteViewsProto.Icon.BLEND_MODE,
+                    values.put(
+                            RemoteViewsProto.Icon.BLEND_MODE,
                             in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
                     break;
                 case (int) RemoteViewsProto.Icon.TINT_LIST:
@@ -101,7 +150,8 @@
                     break;
                 case (int) RemoteViewsProto.Icon.BITMAP:
                     byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
-                    values.put(RemoteViewsProto.Icon.BITMAP,
+                    values.put(
+                            RemoteViewsProto.Icon.BITMAP,
                             BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
                     break;
                 case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
@@ -112,23 +162,27 @@
                                     bitmapAdaptiveData.length));
                     break;
                 case (int) RemoteViewsProto.Icon.RESOURCE:
-                    values.put(RemoteViewsProto.Icon.RESOURCE,
+                    values.put(
+                            RemoteViewsProto.Icon.RESOURCE,
                             in.readString(RemoteViewsProto.Icon.RESOURCE));
                     break;
                 case (int) RemoteViewsProto.Icon.DATA:
-                    values.put(RemoteViewsProto.Icon.DATA,
-                            in.readBytes(RemoteViewsProto.Icon.DATA));
+                    values.put(
+                            RemoteViewsProto.Icon.DATA, in.readBytes(RemoteViewsProto.Icon.DATA));
                     break;
                 case (int) RemoteViewsProto.Icon.URI:
                     values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
                     break;
                 case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
-                    values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+                    values.put(
+                            RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
                             in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
                     break;
                 default:
-                    Log.w(TAG, "Unhandled field while reading Icon proto!\n"
-                            + ProtoUtils.currentFieldToString(in));
+                    Log.w(
+                            TAG,
+                            "Unhandled field while reading Icon proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
             }
         }
 
@@ -174,4 +228,1279 @@
             return icon;
         };
     }
+
+    public static void writeCharSequenceToProto(@NonNull ProtoOutputStream out,
+            @NonNull CharSequence cs) {
+        out.write(RemoteViewsProto.CharSequence.TEXT, cs.toString());
+        if (!(cs instanceof Spanned sp)) return;
+
+        Object[] os = sp.getSpans(0, cs.length(), Object.class);
+        for (Object original : os) {
+            Object prop = original;
+            if (prop instanceof CharacterStyle) {
+                prop = ((CharacterStyle) prop).getUnderlying();
+            }
+
+            final long spansToken = out.start(RemoteViewsProto.CharSequence.SPANS);
+            out.write(RemoteViewsProto.CharSequence.Span.START, sp.getSpanStart(original));
+            out.write(RemoteViewsProto.CharSequence.Span.END, sp.getSpanEnd(original));
+            out.write(RemoteViewsProto.CharSequence.Span.FLAGS, sp.getSpanFlags(original));
+
+            if (prop instanceof AbsoluteSizeSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+                writeAbsoluteSizeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityClickableSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+                writeAccessibilityClickableSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityReplacementSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+                writeAccessibilityReplacementSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AccessibilityURLSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+                writeAccessibilityURLSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof Annotation span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+                writeAnnotationToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof BackgroundColorSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+                writeBackgroundColorSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof BulletSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.BULLET);
+                writeBulletSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof EasyEditSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+                writeEasyEditSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof ForegroundColorSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+                writeForegroundColorSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (noBreakNoHyphenationSpan() && prop instanceof LineBreakConfigSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+                writeLineBreakConfigSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LocaleSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+                writeLocaleSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof QuoteSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+                writeQuoteSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof RelativeSizeSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+                writeRelativeSizeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof ScaleXSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+                writeScaleXSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SpellCheckSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+                writeSpellCheckSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LineBackgroundSpan.Standard span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+                writeLineBackgroundSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LineHeightSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+                writeLineHeightSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof LeadingMarginSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+                writeLeadingMarginSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof AlignmentSpan.Standard span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+                writeAlignmentSpanStandardToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof StrikethroughSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+                writeStrikethroughSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof StyleSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STYLE);
+                writeStyleSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SubscriptSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+                writeSubscriptSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuggestionRangeSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+                writeSuggestionRangeSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuggestionSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+                writeSuggestionSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof SuperscriptSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+                writeSuperscriptSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TextAppearanceSpan span) {
+                final long spanToken = out.start(
+                        RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+                writeTextAppearanceSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TtsSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TTS);
+                writeTtsSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof TypefaceSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+                writeTypefaceSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof URLSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.URL);
+                writeURLSpanToProto(out, span);
+                out.end(spanToken);
+            } else if (prop instanceof UnderlineSpan span) {
+                final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+                writeUnderlineSpanToProto(out, span);
+                out.end(spanToken);
+            }
+            out.end(spansToken);
+        }
+    }
+
+    public static CharSequence createCharSequenceFromProto(ProtoInputStream in) throws Exception {
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        boolean hasSpans = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.TEXT:
+                    String text = in.readString(RemoteViewsProto.CharSequence.TEXT);
+                    builder.append(text);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.SPANS:
+                    hasSpans = true;
+                    final long spansToken = in.start(RemoteViewsProto.CharSequence.SPANS);
+                    createSpanFromProto(in, builder);
+                    in.end(spansToken);
+                    break;
+                default:
+                    Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return hasSpans ? builder : builder.toString();
+    }
+
+    private static void createSpanFromProto(ProtoInputStream in, SpannableStringBuilder builder)
+            throws Exception {
+        int start = 0;
+        int end = 0;
+        int flags = 0;
+        Object what = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.START:
+                    start = in.readInt(RemoteViewsProto.CharSequence.Span.START);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.END:
+                    end = in.readInt(RemoteViewsProto.CharSequence.Span.END);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.FLAGS:
+                    flags = in.readInt(RemoteViewsProto.CharSequence.Span.FLAGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE:
+                    final long asToken = in.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+                    what = createAbsoluteSizeSpanFromProto(in);
+                    in.end(asToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE:
+                    final long acToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+                    what = createAccessibilityClickableSpanFromProto(in);
+                    in.end(acToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT:
+                    final long arToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+                    what = createAccessibilityReplacementSpanFromProto(in);
+                    in.end(arToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL:
+                    final long auToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+                    what = createAccessibilityURLSpanFromProto(in);
+                    in.end(auToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ALIGNMENT:
+                    final long aToken = in.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+                    what = createAlignmentSpanStandardFromProto(in);
+                    in.end(aToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.ANNOTATION:
+                    final long annToken = in.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+                    what = createAnnotationFromProto(in);
+                    in.end(annToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR:
+                    final long bcToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+                    what = createBackgroundColorSpanFromProto(in);
+                    in.end(bcToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.BULLET:
+                    final long bToken = in.start(RemoteViewsProto.CharSequence.Span.BULLET);
+                    what = createBulletSpanFromProto(in);
+                    in.end(bToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.EASY_EDIT:
+                    final long eeToken = in.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+                    what = createEasyEditSpanFromProto(in);
+                    in.end(eeToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR:
+                    final long fcToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+                    what = createForegroundColorSpanFromProto(in);
+                    in.end(fcToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LEADING_MARGIN:
+                    final long lmToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+                    what = createLeadingMarginSpanStandardFromProto(in);
+                    in.end(lmToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND:
+                    final long lbToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+                    what = createLineBackgroundSpanStandardFromProto(in);
+                    in.end(lbToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_BREAK:
+                    if (!noBreakNoHyphenationSpan()) {
+                        continue;
+                    }
+                    final long lbrToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+                    what = createLineBreakConfigSpanFromProto(in);
+                    in.end(lbrToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LINE_HEIGHT:
+                    final long lhToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+                    what = createLineHeightSpanStandardFromProto(in);
+                    in.end(lhToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LOCALE:
+                    final long lToken = in.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+                    what = createLocaleSpanFromProto(in);
+                    in.end(lToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.QUOTE:
+                    final long qToken = in.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+                    what = createQuoteSpanFromProto(in);
+                    in.end(qToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE:
+                    final long rsToken = in.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+                    what = createRelativeSizeSpanFromProto(in);
+                    in.end(rsToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SCALE_X:
+                    final long sxToken = in.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+                    what = createScaleXSpanFromProto(in);
+                    in.end(sxToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SPELL_CHECK:
+                    final long scToken = in.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+                    what = createSpellCheckSpanFromProto(in);
+                    in.end(scToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.STRIKETHROUGH:
+                    final long stToken = in.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+                    what = createStrikethroughSpanFromProto(in);
+                    in.end(stToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.STYLE:
+                    final long sToken = in.start(RemoteViewsProto.CharSequence.Span.STYLE);
+                    what = createStyleSpanFromProto(in);
+                    in.end(sToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUBSCRIPT:
+                    final long suToken = in.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+                    what = createSubscriptSpanFromProto(in);
+                    in.end(suToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE:
+                    final long srToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+                    what = createSuggestionRangeSpanFromProto(in);
+                    in.end(srToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION:
+                    final long sugToken = in.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+                    what = createSuggestionSpanFromProto(in);
+                    in.end(sugToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.SUPERSCRIPT:
+                    final long supToken = in.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+                    what = createSuperscriptSpanFromProto(in);
+                    in.end(supToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE:
+                    final long taToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+                    what = createTextAppearanceSpanFromProto(in);
+                    in.end(taToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TTS:
+                    final long ttsToken = in.start(RemoteViewsProto.CharSequence.Span.TTS);
+                    what = createTtsSpanFromProto(in);
+                    in.end(ttsToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TYPEFACE:
+                    final long tfToken = in.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+                    what = createTypefaceSpanFromProto(in);
+                    in.end(tfToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.UNDERLINE:
+                    final long unToken = in.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+                    what = createUnderlineSpanFromProto(in);
+                    in.end(unToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.URL:
+                    final long urlToken = in.start(RemoteViewsProto.CharSequence.Span.URL);
+                    what = createURLSpanFromProto(in);
+                    in.end(urlToken);
+                    break;
+                default:
+                    Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        if (what == null) {
+            return;
+        }
+        builder.setSpan(what, start, end, flags);
+    }
+
+    public static AbsoluteSizeSpan createAbsoluteSizeSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int size = 0;
+        boolean dip = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE:
+                    size = in.readInt(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP:
+                    dip = in.readBoolean(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP);
+                    break;
+                default:
+                    Log.w("AbsoluteSizeSpan",
+                            "Unhandled field while reading AbsoluteSizeSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AbsoluteSizeSpan(size, dip);
+    }
+
+    public static void writeAbsoluteSizeSpanToProto(@NonNull ProtoOutputStream out,
+            AbsoluteSizeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE, span.getSize());
+        out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP, span.getDip());
+    }
+
+    public static AccessibilityClickableSpan createAccessibilityClickableSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int originalClickableSpanId = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID:
+                    originalClickableSpanId = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID);
+                    break;
+                default:
+                    Log.w("AccessibilityClickable",
+                            "Unhandled field while reading" + " AccessibilityClickableSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityClickableSpan(originalClickableSpanId);
+    }
+
+    public static void writeAccessibilityClickableSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityClickableSpan span) {
+        out.write(
+                RemoteViewsProto.CharSequence.Span
+                        .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID,
+                span.getOriginalClickableSpanId());
+    }
+
+    public static AccessibilityReplacementSpan createAccessibilityReplacementSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        CharSequence contentDescription = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .AccessibilityReplacement.CONTENT_DESCRIPTION:
+                    final long token = in.start(
+                            RemoteViewsProto.CharSequence.Span
+                                    .AccessibilityReplacement.CONTENT_DESCRIPTION);
+                    contentDescription = createCharSequenceFromProto(in);
+                    in.end(token);
+                    break;
+                default:
+                    Log.w("AccessibilityReplacemen", "Unhandled field while reading"
+                            + " AccessibilityReplacementSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityReplacementSpan(contentDescription);
+    }
+
+    public static void writeAccessibilityReplacementSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityReplacementSpan span) {
+        final long token = out.start(
+                RemoteViewsProto.CharSequence.Span.AccessibilityReplacement.CONTENT_DESCRIPTION);
+        CharSequence description = span.getContentDescription();
+        if (description != null) {
+            writeCharSequenceToProto(out, description);
+        }
+        out.end(token);
+    }
+
+    public static AccessibilityURLSpan createAccessibilityURLSpanFromProto(ProtoInputStream in)
+            throws Exception {
+        String url = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL:
+                    url = in.readString(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL);
+                    break;
+                default:
+                    Log.w("AccessibilityURLSpan",
+                            "Unhandled field while reading AccessibilityURLSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AccessibilityURLSpan(new URLSpan(url));
+    }
+
+    public static void writeAccessibilityURLSpanToProto(@NonNull ProtoOutputStream out,
+            AccessibilityURLSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL, span.getURL());
+    }
+
+    public static AlignmentSpan.Standard createAlignmentSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        String alignment = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT:
+                    alignment = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT);
+                    break;
+                default:
+                    Log.w("AlignmentSpan",
+                            "Unhandled field while reading AlignmentSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new AlignmentSpan.Standard(Layout.Alignment.valueOf(alignment));
+    }
+
+    public static void writeAlignmentSpanStandardToProto(@NonNull ProtoOutputStream out,
+            AlignmentSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT,
+                span.getAlignment().name());
+    }
+
+    public static Annotation createAnnotationFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String key = null;
+        String value = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Annotation.KEY:
+                    key = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.KEY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Annotation.VALUE:
+                    value = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.VALUE);
+                    break;
+                default:
+                    Log.w("Annotation", "Unhandled field while reading" + " Annotation proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new Annotation(key, value);
+    }
+
+    public static void writeAnnotationToProto(@NonNull ProtoOutputStream out, Annotation span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Annotation.KEY, span.getKey());
+        out.write(RemoteViewsProto.CharSequence.Span.Annotation.VALUE, span.getValue());
+    }
+
+    public static BackgroundColorSpan createBackgroundColorSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+                    break;
+                default:
+                    Log.w("BackgroundColorSpan",
+                            "Unhandled field while reading" + " BackgroundColorSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new BackgroundColorSpan(color);
+    }
+
+    public static void writeBackgroundColorSpanToProto(@NonNull ProtoOutputStream out,
+            BackgroundColorSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR,
+                span.getBackgroundColor());
+    }
+
+    public static BulletSpan createBulletSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int bulletRadius = 0;
+        int color = 0;
+        int gapWidth = 0;
+        boolean wantColor = false;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS:
+                    bulletRadius = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH:
+                    gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR:
+                    wantColor = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR);
+                    break;
+                default:
+                    Log.w("BulletSpan", "Unhandled field while reading BulletSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new BulletSpan(gapWidth, color, wantColor, bulletRadius);
+    }
+
+    public static void writeBulletSpanToProto(@NonNull ProtoOutputStream out, BulletSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS, span.getBulletRadius());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.COLOR, span.getColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH, span.getGapWidth());
+        out.write(RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR, span.getWantColor());
+    }
+
+    public static EasyEditSpan createEasyEditSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        return new EasyEditSpan();
+    }
+
+    public static void writeEasyEditSpanToProto(@NonNull ProtoOutputStream out, EasyEditSpan span) {
+    }
+
+    public static ForegroundColorSpan createForegroundColorSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+                    break;
+                default:
+                    Log.w("ForegroundColorSpan",
+                            "Unhandled field while reading" + " ForegroundColorSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new ForegroundColorSpan(color);
+    }
+
+    public static LeadingMarginSpan.Standard createLeadingMarginSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int first = 0;
+        int rest = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST:
+                    first = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.REST:
+                    rest = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST);
+                    break;
+                default:
+                    Log.w("LeadingMarginSpan",
+                            "Unhandled field while reading LeadingMarginSpan" + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LeadingMarginSpan.Standard(first, rest);
+    }
+
+    public static void writeLeadingMarginSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LeadingMarginSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST,
+                span.getLeadingMargin(/* first= */ true));
+        out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST,
+                span.getLeadingMargin(/* first= */ false));
+    }
+
+    public static void writeForegroundColorSpanToProto(@NonNull ProtoOutputStream out,
+            ForegroundColorSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.ForegroundColor.COLOR,
+                span.getForegroundColor());
+    }
+
+    public static LineBackgroundSpan.Standard createLineBackgroundSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int color = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineBackground.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR);
+                    break;
+                default:
+                    Log.w("LineBackgroundSpan",
+                            "Unhandled field while reading" + " LineBackgroundSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LineBackgroundSpan.Standard(color);
+    }
+
+    public static void writeLineBackgroundSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LineBackgroundSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR, span.getColor());
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static LineBreakConfigSpan createLineBreakConfigSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int lineBreakStyle = 0;
+        int lineBreakWordStyle = 0;
+        int hyphenation = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE:
+                    lineBreakStyle = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE:
+                    lineBreakWordStyle = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION:
+                    hyphenation = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION);
+                    break;
+                default:
+                    Log.w("LineBreakConfigSpan",
+                            "Unhandled field while reading " + "LineBreakConfigSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        LineBreakConfig lbc = new LineBreakConfig.Builder().setLineBreakStyle(
+                lineBreakStyle).setLineBreakWordStyle(lineBreakWordStyle).setHyphenation(
+                hyphenation).build();
+        return new LineBreakConfigSpan(lbc);
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static void writeLineBreakConfigSpanToProto(@NonNull ProtoOutputStream out,
+            LineBreakConfigSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE,
+                span.getLineBreakConfig().getLineBreakStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE,
+                span.getLineBreakConfig().getLineBreakWordStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION,
+                span.getLineBreakConfig().getHyphenation());
+    }
+
+    public static LineHeightSpan.Standard createLineHeightSpanStandardFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int height = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT:
+                    height = in.readInt(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT);
+                    break;
+                default:
+                    Log.w("LineHeightSpan.Standard",
+                            "Unhandled field while reading" + " LineHeightSpan.Standard proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LineHeightSpan.Standard(height);
+    }
+
+    public static void writeLineHeightSpanStandardToProto(@NonNull ProtoOutputStream out,
+            LineHeightSpan.Standard span) {
+        out.write(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT, span.getHeight());
+    }
+
+    public static LocaleSpan createLocaleSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String languageTags = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS:
+                    languageTags = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS);
+                    break;
+                default:
+                    Log.w("LocaleSpan", "Unhandled field while reading" + " LocaleSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new LocaleSpan(LocaleList.forLanguageTags(languageTags));
+    }
+
+    public static void writeLocaleSpanToProto(@NonNull ProtoOutputStream out, LocaleSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS,
+                span.getLocales().toLanguageTags());
+    }
+
+    public static QuoteSpan createQuoteSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int color = 0;
+        int stripeWidth = 0;
+        int gapWidth = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.COLOR:
+                    color = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH:
+                    stripeWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH:
+                    gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH);
+                    break;
+                default:
+                    Log.w("QuoteSpan", "Unhandled field while reading QuoteSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new QuoteSpan(color, stripeWidth, gapWidth);
+    }
+
+    public static void writeQuoteSpanToProto(@NonNull ProtoOutputStream out, QuoteSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.COLOR, span.getColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH, span.getStripeWidth());
+        out.write(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH, span.getGapWidth());
+    }
+
+    public static RelativeSizeSpan createRelativeSizeSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        float proportion = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION:
+                    proportion = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION);
+                    break;
+                default:
+                    Log.w("RelativeSizeSpan",
+                            "Unhandled field while reading" + " RelativeSizeSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new RelativeSizeSpan(proportion);
+    }
+
+    public static void writeRelativeSizeSpanToProto(@NonNull ProtoOutputStream out,
+            RelativeSizeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION, span.getSizeChange());
+    }
+
+    public static ScaleXSpan createScaleXSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        float proportion = 0f;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION:
+                    proportion = in.readFloat(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION);
+                    break;
+                default:
+                    Log.w("ScaleXSpan", "Unhandled field while reading" + " ScaleXSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new ScaleXSpan(proportion);
+    }
+
+    public static void writeScaleXSpanToProto(@NonNull ProtoOutputStream out, ScaleXSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION, span.getScaleX());
+    }
+
+    public static SpellCheckSpan createSpellCheckSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SpellCheckSpan();
+    }
+
+    public static void writeSpellCheckSpanToProto(@NonNull ProtoOutputStream out,
+            SpellCheckSpan span) {
+    }
+
+    public static StrikethroughSpan createStrikethroughSpanFromProto(@NonNull ProtoInputStream in) {
+        return new StrikethroughSpan();
+    }
+
+    public static void writeStrikethroughSpanToProto(@NonNull ProtoOutputStream out,
+            StrikethroughSpan span) {
+    }
+
+    public static StyleSpan createStyleSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        int style = 0;
+        int fontWeightAdjustment = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Style.STYLE:
+                    style = in.readInt(RemoteViewsProto.CharSequence.Span.Style.STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT:
+                    fontWeightAdjustment = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT);
+                    break;
+                default:
+                    Log.w("StyleSpan", "Unhandled field while reading StyleSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new StyleSpan(style, fontWeightAdjustment);
+    }
+
+    public static void writeStyleSpanToProto(@NonNull ProtoOutputStream out, StyleSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Style.STYLE, span.getStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT,
+                span.getFontWeightAdjustment());
+    }
+
+    public static SubscriptSpan createSubscriptSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SubscriptSpan();
+    }
+
+    public static void writeSubscriptSpanToProto(@NonNull ProtoOutputStream out,
+            SubscriptSpan span) {
+    }
+
+    public static SuggestionRangeSpan createSuggestionRangeSpanFromProto(
+            @NonNull ProtoInputStream in) throws Exception {
+        int backgroundColor = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR:
+                    backgroundColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR);
+                    break;
+                default:
+                    Log.w("SuggestionRangeSpan",
+                            "Unhandled field while reading" + " SuggestionRangeSpan proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        SuggestionRangeSpan span = new SuggestionRangeSpan();
+        span.setBackgroundColor(backgroundColor);
+        return span;
+    }
+
+    public static void writeSuggestionRangeSpanToProto(@NonNull ProtoOutputStream out,
+            SuggestionRangeSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR,
+                span.getBackgroundColor());
+    }
+
+    public static SuggestionSpan createSuggestionSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        List<String> suggestions = new ArrayList<>();
+        int flags = 0;
+        String localeStringForCompatibility = null;
+        String languageTag = null;
+        int hashCode = 0;
+        int easyCorrectUnderlineColor = 0;
+        float easyCorrectUnderlineThickness = 0;
+        int misspelledUnderlineColor = 0;
+        float misspelledUnderlineThickness = 0;
+        int autoCorrectionUnderlineColor = 0;
+        float autoCorrectionUnderlineThickness = 0;
+        int grammarErrorUnderlineColor = 0;
+        float grammarErrorUnderlineThickness = 0;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS:
+                    suggestions.add(in.readString(
+                            RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS));
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS:
+                    flags = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY:
+                    localeStringForCompatibility = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG:
+                    languageTag = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE:
+                    hashCode = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.EASY_CORRECT_UNDERLINE_COLOR:
+                    easyCorrectUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.EASY_CORRECT_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS:
+                    easyCorrectUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.MISSPELLED_UNDERLINE_COLOR:
+                    misspelledUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.MISSPELLED_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.MISSPELLED_UNDERLINE_THICKNESS:
+                    misspelledUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.MISSPELLED_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR:
+                    autoCorrectionUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS:
+                    autoCorrectionUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR:
+                    grammarErrorUnderlineColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS:
+                    grammarErrorUnderlineThickness = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span
+                                    .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS);
+                    break;
+                default:
+                    Log.w("SuggestionSpan",
+                            "Unhandled field while reading SuggestionSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        String[] suggestionsArray = new String[suggestions.size()];
+        suggestions.toArray(suggestionsArray);
+        return new SuggestionSpan(suggestionsArray, flags, localeStringForCompatibility,
+                languageTag, hashCode, easyCorrectUnderlineColor, easyCorrectUnderlineThickness,
+                misspelledUnderlineColor, misspelledUnderlineThickness,
+                autoCorrectionUnderlineColor, autoCorrectionUnderlineThickness,
+                grammarErrorUnderlineColor, grammarErrorUnderlineThickness);
+    }
+
+    public static void writeSuggestionSpanToProto(@NonNull ProtoOutputStream out,
+            SuggestionSpan span) {
+        for (String suggestion : span.getSuggestions()) {
+            out.write(RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS, suggestion);
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS, span.getFlags());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LOCALE_STRING_FOR_COMPATIBILITY,
+                span.getLocale());
+        if (span.getLocaleObject() != null) {
+            out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG,
+                    span.getLocaleObject().toLanguageTag());
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE, span.hashCode());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_COLOR,
+                span.getEasyCorrectUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS,
+                span.getEasyCorrectUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_COLOR,
+                span.getMisspelledUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_THICKNESS,
+                span.getMisspelledUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR,
+                span.getAutoCorrectionUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS,
+                span.getAutoCorrectionUnderlineThickness());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR,
+                span.getGrammarErrorUnderlineColor());
+        out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS,
+                span.getGrammarErrorUnderlineThickness());
+    }
+
+    public static SuperscriptSpan createSuperscriptSpanFromProto(@NonNull ProtoInputStream in) {
+        return new SuperscriptSpan();
+    }
+
+    public static void writeSuperscriptSpanToProto(@NonNull ProtoOutputStream out,
+            SuperscriptSpan span) {
+    }
+
+    public static TextAppearanceSpan createTextAppearanceSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String familyName = null;
+        int style = 0;
+        int textSize = 0;
+        ColorStateList textColor = null;
+        ColorStateList textColorLink = null;
+        int textFontWeight = 0;
+        LocaleList textLocales = null;
+        float shadowRadius = 0F;
+        float shadowDx = 0F;
+        float shadowDy = 0F;
+        int shadowColor = 0;
+        boolean hasElegantTextHeight = false;
+        boolean elegantTextHeight = false;
+        boolean hasLetterSpacing = false;
+        float letterSpacing = 0F;
+        String fontFeatureSettings = null;
+        String fontVariationSettings = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME:
+                    familyName = in.readString(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE:
+                    style = in.readInt(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE:
+                    textSize = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR:
+                    final long textColorToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+                    textColor = ColorStateList.createFromProto(in);
+                    in.end(textColorToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK:
+                    final long textColorLinkToken = in.start(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+                    textColorLink = ColorStateList.createFromProto(in);
+                    in.end(textColorLinkToken);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT:
+                    textFontWeight = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE:
+                    textLocales = LocaleList.forLanguageTags(in.readString(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE));
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS:
+                    shadowRadius = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX:
+                    shadowDx = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY:
+                    shadowDy = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR:
+                    shadowColor = in.readInt(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD:
+                    hasElegantTextHeight = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT:
+                    elegantTextHeight = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.HAS_LETTER_SPACING_FIELD:
+                    hasLetterSpacing = in.readBoolean(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.HAS_LETTER_SPACING_FIELD);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING:
+                    letterSpacing = in.readFloat(
+                            RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS:
+                    fontFeatureSettings = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.FONT_FEATURE_SETTINGS);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span
+                        .TextAppearance.FONT_VARIATION_SETTINGS:
+                    fontVariationSettings = in.readString(
+                            RemoteViewsProto.CharSequence.Span
+                                    .TextAppearance.FONT_VARIATION_SETTINGS);
+                    break;
+                default:
+                    Log.w("TextAppearanceSpan",
+                            "Unhandled field while reading TextAppearanceSpan " + "proto!\n"
+                                    + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TextAppearanceSpan(familyName, style, textSize, textColor, textColorLink,
+                /* typeface= */ null, textFontWeight, textLocales, shadowRadius, shadowDx, shadowDy,
+                shadowColor, hasElegantTextHeight, elegantTextHeight, hasLetterSpacing,
+                letterSpacing, fontFeatureSettings, fontVariationSettings);
+    }
+
+    public static void writeTextAppearanceSpanToProto(@NonNull ProtoOutputStream out,
+            TextAppearanceSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME, span.getFamily());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE, span.getTextStyle());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE, span.getTextSize());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT,
+                span.getTextFontWeight());
+        if (span.getTextLocales() != null) {
+            out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE,
+                    span.getTextLocales().toLanguageTags());
+        }
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS,
+                span.getShadowRadius());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX, span.getShadowDx());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY, span.getShadowDy());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR,
+                span.getShadowColor());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD,
+                span.hasElegantTextHeight());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT,
+                span.isElegantTextHeight());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_LETTER_SPACING_FIELD,
+                span.hasLetterSpacing());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING,
+                span.getLetterSpacing());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS,
+                span.getFontFeatureSettings());
+        out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_VARIATION_SETTINGS,
+                span.getFontVariationSettings());
+        if (span.getTextColor() != null) {
+            final long textColorToken = out.start(
+                    RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+            span.getTextColor().writeToProto(out);
+            out.end(textColorToken);
+        }
+        if (span.getLinkTextColor() != null) {
+            final long textColorLinkToken = out.start(
+                    RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+            span.getLinkTextColor().writeToProto(out);
+            out.end(textColorLinkToken);
+        }
+    }
+
+    public static TtsSpan createTtsSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+        String type = null;
+        PersistableBundle args = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Tts.TYPE:
+                    type = in.readString(RemoteViewsProto.CharSequence.Span.Tts.TYPE);
+                    break;
+                case (int) RemoteViewsProto.CharSequence.Span.Tts.ARGS:
+                    final byte[] data = in.readString(
+                            RemoteViewsProto.CharSequence.Span.Tts.ARGS).getBytes();
+                    args = PersistableBundle.readFromStream(new ByteArrayInputStream(data));
+                    break;
+                default:
+                    Log.w("TtsSpan", "Unhandled field while reading TtsSpan " + "proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TtsSpan(type, args);
+    }
+
+    public static void writeTtsSpanToProto(@NonNull ProtoOutputStream out, TtsSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Tts.TYPE, span.getType());
+        if (span.getArgs() != null) {
+            ByteArrayOutputStream buf = new ByteArrayOutputStream();
+            try {
+                span.getArgs().writeToStream(buf);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            out.write(RemoteViewsProto.CharSequence.Span.Tts.ARGS, buf.toString(UTF_8));
+        }
+    }
+
+    public static TypefaceSpan createTypefaceSpanFromProto(@NonNull ProtoInputStream in)
+            throws Exception {
+        String family = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Typeface.FAMILY:
+                    family = in.readString(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY);
+                    break;
+                default:
+                    Log.w("TypefaceSpan", "Unhandled field while reading" + " TypefaceSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new TypefaceSpan(family);
+    }
+
+    public static void writeTypefaceSpanToProto(@NonNull ProtoOutputStream out, TypefaceSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY, span.getFamily());
+    }
+
+    public static URLSpan createURLSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+        String url = null;
+        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (in.getFieldNumber()) {
+                case (int) RemoteViewsProto.CharSequence.Span.Url.URL:
+                    url = in.readString(RemoteViewsProto.CharSequence.Span.Url.URL);
+                    break;
+                default:
+                    Log.w("URLSpan", "Unhandled field while reading" + " URLSpan proto!\n"
+                            + ProtoUtils.currentFieldToString(in));
+            }
+        }
+        return new URLSpan(url);
+    }
+
+    public static void writeURLSpanToProto(@NonNull ProtoOutputStream out, URLSpan span) {
+        out.write(RemoteViewsProto.CharSequence.Span.Url.URL, span.getURL());
+    }
+
+    public static UnderlineSpan createUnderlineSpanFromProto(@NonNull ProtoInputStream in) {
+        return new UnderlineSpan();
+    }
+
+    public static void writeUnderlineSpanToProto(@NonNull ProtoOutputStream out,
+            UnderlineSpan span) {
+    }
 }
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index f0144cb..20d1b3b 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -72,6 +72,7 @@
     int mAppearance;
     private final boolean mIsTranslucent;
     private final boolean mHasImeSurface;
+    private final int mUiMode;
     // Must be one of the named color spaces, otherwise, always use SRGB color space.
     private final ColorSpace mColorSpace;
     private int mInternalReferences;
@@ -96,7 +97,7 @@
             Rect contentInsets, Rect letterboxInsets, boolean isLowResolution,
             boolean isRealSnapshot, int windowingMode,
             @WindowInsetsController.Appearance int appearance, boolean isTranslucent,
-            boolean hasImeSurface) {
+            boolean hasImeSurface, int uiMode) {
         mId = id;
         mCaptureTime = captureTime;
         mTopActivityComponent = topActivityComponent;
@@ -114,6 +115,7 @@
         mAppearance = appearance;
         mIsTranslucent = isTranslucent;
         mHasImeSurface = hasImeSurface;
+        mUiMode = uiMode;
     }
 
     private TaskSnapshot(Parcel source) {
@@ -136,6 +138,7 @@
         mAppearance = source.readInt();
         mIsTranslucent = source.readBoolean();
         mHasImeSurface = source.readBoolean();
+        mUiMode = source.readInt();
     }
 
     /**
@@ -273,6 +276,13 @@
         return mAppearance;
     }
 
+    /**
+     * @return The uiMode the screenshot was taken in.
+     */
+    public int getUiMode() {
+        return mUiMode;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -295,6 +305,7 @@
         dest.writeInt(mAppearance);
         dest.writeBoolean(mIsTranslucent);
         dest.writeBoolean(mHasImeSurface);
+        dest.writeInt(mUiMode);
     }
 
     @Override
@@ -318,7 +329,8 @@
                 + " mAppearance=" + mAppearance
                 + " mIsTranslucent=" + mIsTranslucent
                 + " mHasImeSurface=" + mHasImeSurface
-                + " mInternalReferences=" + mInternalReferences;
+                + " mInternalReferences=" + mInternalReferences
+                + " mUiMode=" + Integer.toHexString(mUiMode);
     }
 
     /**
@@ -370,6 +382,7 @@
         private boolean mIsTranslucent;
         private boolean mHasImeSurface;
         private int mPixelFormat;
+        private int mUiMode;
 
         public Builder setId(long id) {
             mId = id;
@@ -452,6 +465,14 @@
             return this;
         }
 
+        /**
+         * Sets the original uiMode while capture
+         */
+        public Builder setUiMode(int uiMode) {
+            mUiMode = uiMode;
+            return this;
+        }
+
         public int getPixelFormat() {
             return mPixelFormat;
         }
@@ -481,7 +502,8 @@
                     mWindowingMode,
                     mAppearance,
                     mIsTranslucent,
-                    mHasImeSurface);
+                    mHasImeSurface,
+                    mUiMode);
 
         }
     }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index e9d77f8..314bf89 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -700,6 +700,18 @@
     }
 
     /**
+     * Restore the back navigation target from visible to invisible for canceling gesture animation.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction restoreBackNavi() {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+    /**
      * Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
      *
      * @param receiver The window container that the insets source is added to.
@@ -1436,6 +1448,7 @@
         public static final int HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION = 17;
         public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
         public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
+        public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 125a0b2..4f84817 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -218,3 +218,10 @@
     description: "Enables desktop windowing app-to-web education"
     bug: "348205896"
 }
+
+flag {
+    name: "enable_minimize_button"
+    namespace: "lse_desktop_experience"
+    description: "Adds a minimize button the the caption bar"
+    bug: "356843241"
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 2daf0fd..921363c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -108,7 +108,6 @@
      * @param backDisposition disposition flags
      * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
      * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
-     * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
      */
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index b316a01..12d3264 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -727,11 +727,11 @@
         this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
                 TextUtils.safeIntern(libraryName));
         this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
-                this.usesSdkLibrariesVersionsMajor, versionMajor, true);
+                this.usesSdkLibrariesVersionsMajor, versionMajor, /* allowDuplicates= */ true);
         this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
-                this.usesSdkLibrariesCertDigests, certSha256Digests, true);
-        this.usesSdkLibrariesOptional = ArrayUtils.appendBoolean(this.usesSdkLibrariesOptional,
-                usesSdkLibrariesOptional);
+                this.usesSdkLibrariesCertDigests, certSha256Digests, /* allowDuplicates= */ true);
+        this.usesSdkLibrariesOptional = ArrayUtils.appendBooleanDuplicatesAllowed(
+                this.usesSdkLibrariesOptional, usesSdkLibrariesOptional);
         return this;
     }
 
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 8f00f79..1e2cad4 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -620,10 +620,10 @@
     }
 
     /**
-     * Adds value to given array if not already present, providing set-like
-     * behavior.
+     * Adds value to given array. The method allows duplicate values.
      */
-    public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+    public static boolean[] appendBooleanDuplicatesAllowed(@Nullable boolean[] cur,
+            boolean val) {
         if (cur == null) {
             return new boolean[] { val };
         }
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 37d1c5b..5892396 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -89,6 +89,205 @@
             bytes adaptive_bitmap = 8;
         };
     }
+
+    /**
+     * Represents a CharSequence with Spans.
+     */
+    message CharSequence {
+        optional string text = 1;
+        repeated Span spans = 2;
+
+        message Span {
+            optional int32 start = 1;
+            optional int32 end = 2;
+            optional int32 flags = 3;
+            // We use `repeated` for the following fields so that ProtoOutputStream does not omit
+            // empty messages (e.g. EasyEdit, Superscript). In practice, only one of the following
+            // fields will be written per Span message. We cannot use `oneof` here because
+            // ProtoOutputStream will omit empty messages.
+            repeated AbsoluteSize absolute_size = 4;
+            repeated AccessibilityClickable accessibility_clickable = 5;
+            repeated AccessibilityReplacement accessibility_replacement = 6;
+            repeated AccessibilityUrl accessibility_url = 7;
+            repeated Alignment alignment = 8;
+            repeated Annotation annotation = 9;
+            repeated BackgroundColor background_color = 10;
+            repeated Bullet bullet = 11;
+            repeated EasyEdit easy_edit = 12;
+            repeated ForegroundColor foreground_color = 13;
+            repeated LeadingMargin leading_margin = 14;
+            repeated LineBackground line_background = 15;
+            repeated LineBreak line_break = 16;
+            repeated LineHeight line_height = 17;
+            repeated Locale locale = 18;
+            repeated Quote quote = 19;
+            repeated RelativeSize relative_size = 20;
+            repeated ScaleX scale_x = 21;
+            repeated SpellCheck spell_check = 22;
+            repeated Strikethrough strikethrough = 23;
+            repeated Style style = 24;
+            repeated Subscript subscript = 25;
+            repeated Suggestion suggestion = 26;
+            repeated SuggestionRange suggestion_range = 27;
+            repeated Superscript superscript = 28;
+            repeated TextAppearance text_appearance = 29;
+            repeated Tts tts = 30;
+            repeated Typeface typeface = 31;
+            repeated Underline underline = 32;
+            repeated Url url = 33;
+
+            message AbsoluteSize {
+                optional int32 size = 1;
+                optional bool dip = 2;
+            }
+
+            message AccessibilityClickable {
+                optional int32 original_clickable_span_id = 1;
+            }
+
+            message AccessibilityReplacement {
+                optional CharSequence content_description = 1;
+            }
+
+            message AccessibilityUrl {
+                optional string url = 1;
+            }
+
+            message Alignment {
+                optional string alignment = 1;
+            }
+
+            message Annotation {
+                optional string key = 1;
+                optional string value = 2;
+            }
+
+            message BackgroundColor {
+                optional int32 color = 1;
+            }
+
+            message Bullet {
+                optional int32 gap_width = 1;
+                optional int32 color = 2;
+                optional int32 bullet_radius = 3;
+                optional bool want_color = 4;
+            }
+
+            message EasyEdit {}
+
+            message ForegroundColor {
+                optional int32 color = 1;
+            }
+
+            message LeadingMargin {
+                optional int32 first = 1;
+                optional int32 rest = 2;
+            }
+
+            message LineBackground {
+                optional int32 color = 1;
+            }
+
+            message LineBreak {
+                optional int32 line_break_style = 1;
+                optional int32 line_break_word_style = 2;
+                optional int32 hyphenation = 3;
+            }
+
+            message LineHeight {
+                optional int32 height = 1;
+            }
+
+            message Locale {
+                optional string language_tags = 1;
+            }
+
+            message Quote {
+                optional int32 color = 1;
+                optional int32 stripe_width = 2;
+                optional int32 gap_width = 3;
+            }
+
+            message RelativeSize {
+                optional float proportion = 1;
+            }
+
+            message ScaleX {
+                optional float proportion = 1;
+            }
+
+            message SpellCheck {
+                optional bool in_progress = 1;
+            }
+
+            message Strikethrough {}
+
+            message Style {
+                optional int32 style = 1;
+                optional int32 font_weight_adjustment = 2;
+            }
+
+            message Subscript {}
+
+            message Suggestion {
+                repeated string suggestions = 1;
+                optional int32 flags = 2;
+                optional string locale_string_for_compatibility = 3;
+                optional string language_tag = 4;
+                optional int32 hash_code = 5;
+                optional int32 easy_correct_underline_color = 6;
+                optional float easy_correct_underline_thickness = 7;
+                optional int32 misspelled_underline_color = 8;
+                optional float misspelled_underline_thickness = 9;
+                optional int32 auto_correction_underline_color = 10;
+                optional float auto_correction_underline_thickness = 11;
+                optional int32 grammar_error_underline_color = 12;
+                optional float grammar_error_underline_thickness = 13;
+            }
+
+            message SuggestionRange {
+                optional int32 background_color = 1;
+            }
+
+            message Superscript {}
+
+            // Typeface is omitted
+            message TextAppearance {
+                optional string family_name = 1;
+                optional int32 style = 2;
+                optional int32 text_size = 3;
+                optional android.content.res.ColorStateListProto text_color = 4;
+                optional android.content.res.ColorStateListProto text_color_link = 5;
+                optional int32 text_font_weight = 7;
+                optional string text_locale = 8;
+                optional float shadow_radius = 9;
+                optional float shadow_dx = 10;
+                optional float shadow_dy = 11;
+                optional int32 shadow_color = 12;
+                optional bool has_elegant_text_height_field = 13;
+                optional bool elegant_text_height = 14;
+                optional bool has_letter_spacing_field = 15;
+                optional float letter_spacing = 16;
+                optional string font_feature_settings = 17;
+                optional string font_variation_settings = 18;
+            }
+
+            message Tts {
+                optional string type = 1;
+                optional string args = 2;
+            }
+
+            message Typeface {
+                optional string family = 1;
+            }
+
+            message Underline {}
+
+            message Url {
+                optional string url = 1;
+            }
+        }
+    }
 }
 
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 50727a2..7aeabee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8132,6 +8132,12 @@
     <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows low-level access to monitor keyboard system shortcuts
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS"
+                android:protectionLevel="signature" />
+
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
 
     <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml
index 6bb969b..610a212 100644
--- a/core/res/res/layout/input_method_switch_dialog_new.xml
+++ b/core/res/res/layout/input_method_switch_dialog_new.xml
@@ -17,25 +17,35 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <com.android.internal.widget.MaxHeightFrameLayout
-        android:layout_width="320dp"
+    <LinearLayout
+        android:layout_width="wrap_content"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:maxHeight="373dp">
+        android:orientation="horizontal">
 
-        <com.android.internal.widget.RecyclerView
-            android:id="@+id/list"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingVertical="8dp"
-            android:clipToPadding="false"
-            android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+        <!-- TODO(b/357644229): Enable shrinking width without three levels of nesting. -->
+        <com.android.internal.widget.MaxHeightFrameLayout
+            android:layout_width="320dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:maxHeight="373dp">
 
-    </com.android.internal.widget.MaxHeightFrameLayout>
+            <com.android.internal.widget.RecyclerView
+                android:id="@+id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingVertical="8dp"
+                android:clipToPadding="false"
+                android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+
+        </com.android.internal.widget.MaxHeightFrameLayout>
+
+    </LinearLayout>
 
     <LinearLayout
         style="?android:attr/buttonBarStyle"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bdcba9d..0d16e9c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5462,7 +5462,9 @@
   <java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
   <java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
 
+  <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
   <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
+  <java-symbol type="xml" name="haptic_feedback_customization" />
 
   <!-- For ActivityManager PSS profiling configurability -->
   <java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/core/res/res/xml/haptic_feedback_customization.xml
similarity index 68%
copy from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
copy to core/res/res/xml/haptic_feedback_customization.xml
index cd7a879..7ac0787 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/core/res/res/xml/haptic_feedback_customization.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2024 The Android Open Source Project
   ~
@@ -14,9 +15,4 @@
   ~ limitations under the License.
   -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.internal.overlay.hsumconfig"
-    android:versionCode="1"
-    android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
-</manifest>
+<haptic-feedback-constants/>
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 78cd1e1..e7009d14 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -61,4 +61,28 @@
     <style name="IsFrameRatePowerSavingsBalancedEnabled">
         <item name="android:windowIsFrameRatePowerSavingsBalanced">true</item>
     </style>
+    <style name="customFont">
+        <item name="android:fontFamily">@font/samplefont</item>
+    </style>
+    <style name="customFontWithStyle">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+    </style>
+    <style name="textAppearanceWithAllAttributes">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+        <item name="android:textSize">160dp</item>
+        <item name="android:textColor">#FF00FF</item>
+        <item name="android:textColorLink">#00FFFF</item>
+        <item name="android:textLocale">ja-JP,zh-CN</item>
+        <item name="android:shadowColor">#00FFFF</item>
+        <item name="android:shadowDx">1.0</item>
+        <item name="android:shadowDy">2.0</item>
+        <item name="android:shadowRadius">3.0</item>
+        <item name="android:elegantTextHeight">true</item>
+        <item name="android:letterSpacing">1.0</item>
+        <item name="android:fontFeatureSettings">\"smcp\"</item>
+        <item name="android:fontVariationSettings">\'wdth\' 150</item>
+    </style>
+
 </resources>
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 2f336ab..e2c1902 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -20,6 +20,10 @@
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -30,7 +34,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.text.flags.Flags;
+
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,6 +48,9 @@
     private static View sView;
     private static final String TEXT = "abc def";
 
+    @Rule
+    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @BeforeClass
     public static void setupClass() {
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -76,11 +86,13 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_charAt_editing() {
         transformedText_charAt_editing(false, "\n\n");
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_charAt_singleLine_editing() {
         transformedText_charAt_editing(true, "\uFFFD");
     }
@@ -132,6 +144,64 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_charAt_editing_stickyHighlightRange() {
+        transformedText_charAt_editing_stickyHighlightRange(false, "\n\n");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_charAt_singleLine_editing_stickyHighlightRange() {
+        transformedText_charAt_editing_stickyHighlightRange(true, "\uFFFD");
+    }
+
+    private void transformedText_charAt_editing_stickyHighlightRange(boolean singleLine,
+            String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        assertCharSequence(transformedText,  "abc" + placeholder + " def");
+
+        // original text is "abcxx def" after insertion.
+        text.insert(3, "xx");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+        // original text is "abcxx vvdef" after insertion.
+        text.insert(6, "vv");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+        // original text is "abc vvdef" after deletion.
+        text.delete(3, 5);
+        assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+        // original text is "abc def" after deletion.
+        text.delete(4, 6);
+        assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+        // original text is "abdef" after deletion.
+        // deletion range covers the placeholder's insertion point. It'll try to stay the same,
+        // which is still at index 3.
+        text.delete(2, 4);
+        assertCharSequence(transformedText, "abd" + placeholder + "ef");
+
+        // original text is "axxdef" after replace.
+        // this time the replaced range is ahead of the placeholder's insertion point. It updates to
+        // index 4.
+        text.replace(1, 2, "xx");
+        assertCharSequence(transformedText, "axxd" + placeholder + "ef");
+
+        // original text is "ax" after replace.
+        // the deleted range covers the placeholder's insertion point. It tries to stay at index 4.
+        // However, 4 out of bounds now. So placeholder is inserted at the end of the string.
+        text.delete(2, 6);
+        assertCharSequence(transformedText, "ax" + placeholder);
+    }
+
+    @Test
     public void transformedText_subSequence() {
         for (int offset = 0; offset < TEXT.length(); ++offset) {
             final InsertModeTransformationMethod transformationMethod =
@@ -697,7 +767,7 @@
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+    public void transformedText_getHighlightStartAndEnd_singleLine_insertion() {
         transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
     }
 
@@ -751,16 +821,18 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_getHighlightStartAndEnd_deletion() {
         transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_deletion() {
         transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
     }
 
-    public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+    private void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
             String placeholder) {
         final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
         final InsertModeTransformationMethod transformationMethod =
@@ -816,14 +888,93 @@
         assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(false, "\n\n");
+    }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_deletion_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(true, "\uFDDD");
+    }
+
+    private void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(
+            boolean singleLine, String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abxxxxxx def" after deletion.
+        // the placeholder is now inserted at index 6.
+        // the highlight start is 2, since the deletion happens before the highlight range.
+        // the highlight end now is 8 + placeholder.length().
+        text.delete(2, 3);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+        // original text is "abxxx def" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens in the highlight range.
+        // the highlight end now is 5 + placeholder.length().
+        text.delete(2, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "abxxx d" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens after the highlight range.
+        // the highlight end now is still 5 + placeholder.length().
+        text.delete(7, 9);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "axx d" after deletion.
+        // the placeholder is now inserted at index 3.
+        // the highlight start is at 2, since the deletion range covers the start.
+        // the highlight end is 3 + placeholder.length().
+        text.delete(1, 3);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "ax" after deletion.
+        // the placeholder is now inserted at index 2.
+        // the highlight start is at 2.
+        // the highlight end is 2 + placeholder.length(). It wants to stay at 3, but it'll be out
+        // of bounds, so it'll be 2 instead.
+        text.delete(2, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(2 + placeholder.length());
+    }
+
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
     public void transformedText_getHighlightStartAndEnd_replace() {
         transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
     }
 
     @Test
-    public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+    @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_replace() {
         transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
     }
 
@@ -908,6 +1059,99 @@
         assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(false, "\n\n");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+    public void transformedText_getHighlightStartAndEnd_singleLine_replace_stickyHighlightRange() {
+        transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(true, "\uFDDD");
+    }
+
+    private void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(
+            boolean singleLine, String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abvvxxxxxx def" after replace.
+        // the replacement happens before the highlight range; highlight range is offset by 1
+        // the placeholder is now inserted at index 10,
+        // the highlight start is 4.
+        // the highlight end is 10 + placeholder.length().
+        text.replace(2, 3, "vv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+        // original text is "abvvxxx def" after replace.
+        // the replacement happens in the highlight range; highlight end is offset by -3
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(5, 9, "x");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abvvxxxvvv" after replace.
+        // the replacement happens after the highlight range; highlight is not changed
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(7, 11, "vvv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abxxxxvvv" after replace.
+        // the replacement covers the highlight start; highlight start stays the same;
+        // highlight end is offset by -1
+        // the placeholder is now inserted at index 6,
+        // the highlight start is 4.
+        // the highlight end is 6 + placeholder.length().
+        text.replace(2, 5, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+        // original text is "abxxxxxvv" after replace.
+        // the replacement covers the highlight end; highlight end stays the same;
+        // highlight start stays the same
+        // the placeholder is now inserted at index 6,
+        // the highlight start is 2.
+        // the highlight end is 6 + placeholder.length().
+        text.replace(5, 7, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+        // original text is "axxv" after replace.
+        // the replacement covers the highlight range; highlight start stays the same.
+        // highlight end shrink to the text length.
+        // the placeholder is now inserted at index 3,
+        // the highlight start is 2.
+        // the highlight end is 4 + placeholder.length().
+        text.replace(1, 8, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(4 + placeholder.length());
+    }
+
     private static  <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
             Class<T> type) {
         int currentTransition = 0;
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
index b9a9557..ac6c19e 100644
--- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -16,16 +16,20 @@
 
 package android.view;
 
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
 import static android.view.HapticFeedbackConstants.SCROLL_TICK;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
@@ -33,17 +37,24 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
+// TODO(b/353625893): update old tests to use new infra like those with "inputDeviceCustomized".
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public final class HapticScrollFeedbackProviderTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final int INPUT_DEVICE_1 = 1;
     private static final int INPUT_DEVICE_2 = 2;
 
@@ -64,6 +75,7 @@
         mView = new TestView(InstrumentationRegistry.getContext());
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
                 /* disabledIfViewPlaysScrollHaptics= */ true);
+        mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
     }
 
     @Test
@@ -85,6 +97,26 @@
     }
 
     @Test
+    public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollTickInterval(5);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() {
         mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
                 /* disabledIfViewPlaysScrollHaptics= */ false);
@@ -107,6 +139,35 @@
     }
 
     @Test
+    public void testRotaryEncoder_inputDeviceCustomized_feedbackWhenDisregardingViewBasedScrollHaptics() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+                /* disabledIfViewPlaysScrollHaptics= */ false);
+        when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+                .thenReturn(true);
+        setHapticScrollTickInterval(5);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testNoFeedbackWhenFeedbackIsDisabled() {
         setHapticScrollFeedbackEnabled(false);
         // Call different types scroll feedback methods; non of them should produce feedback because
@@ -130,6 +191,31 @@
     }
 
     @Test
+    public void testNoFeedbackWhenFeedbackIsDisabled_inputDeviceCustomized() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        setHapticScrollFeedbackEnabled(false);
+        // Call different types scroll feedback methods; non of them should produce feedback because
+        // feedback has been disabled.
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 300);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -300);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testSnapToItem() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -138,6 +224,25 @@
     }
 
     @Test
+    public void testSnapToItem_inputDeviceCustomized() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+        requests.add(
+                new HapticFeedbackRequest(
+                        SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_start() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -150,6 +255,24 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_start() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_stop() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -162,6 +285,24 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_stop() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_zeroTickInterval() {
         setHapticScrollTickInterval(0);
 
@@ -176,6 +317,22 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_zeroTickInterval() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+        setHapticScrollTickInterval(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 30);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+    }
+
+    @Test
     public void testScrollProgress_progressEqualsOrExceedsPositiveThreshold() {
         setHapticScrollTickInterval(100);
         mProvider.onScrollProgress(
@@ -198,6 +355,32 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsPositiveThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 120);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_progressEqualsOrExceedsNegativeThreshold() {
         setHapticScrollTickInterval(100);
 
@@ -224,6 +407,35 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsNegativeThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -20);
+
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -80);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -70);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -40);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_positiveAndNegativeProgresses() {
         setHapticScrollTickInterval(100);
 
@@ -262,6 +474,54 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_positiveAndNegativeProgresses() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 20);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -90);
+
+        // total pixel abs = 70
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 10);
+
+        // total pixel abs = 60
+        assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -50);
+        // total pixel abs = 110. Passed threshold. total pixel reduced to -10.
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 40);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 50);
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 60);
+        // total pixel abs = 140. Passed threshold. total pixel reduced to 40.
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollProgress_singleProgressExceedsThreshold() {
         setHapticScrollTickInterval(100);
 
@@ -273,6 +533,21 @@
     }
 
     @Test
+    public void testScrollProgress_inputDeviceCustomized_singleProgressExceedsThreshold() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+        setHapticScrollTickInterval(100);
+
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 1000);
+        requests.add(new HapticFeedbackRequest(
+                    SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -288,6 +563,29 @@
     }
 
     @Test
+    public void testScrollLimit_startAndEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // start after end NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -303,6 +601,29 @@
     }
 
     @Test
+    public void testScrollLimit_doubleStartLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 1st start played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd start NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -318,6 +639,29 @@
     }
 
     @Test
+    public void testScrollLimit_doubleEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 1st end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd end NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_notEnabledWithZeroProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -339,6 +683,36 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_notEnabledWithZeroProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        // progress 0. scroll not started.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 0);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ true);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -357,6 +731,36 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // No tick since tick-interval is default 0, which means no tick.
+        // But still re-enable next limit feedback.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        // scroll pixel not 0, so end played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithSnap() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -374,6 +778,35 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithSnap() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        // 1st enabled limit by snap
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // 2nd enabled limit by snap
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_notEnabledWithDissimilarSnap() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -391,6 +824,33 @@
     }
 
     @Test
+    public void testScrollLimit_inputDeviceCustomized_notEnabledWithDissimilarSnap() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_X);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // Last snap is on AXIS_X, so end on AXIS_SCROLL is NOT played.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
     public void testScrollLimit_enabledWithDissimilarProgress() {
         mProvider.onSnapToItem(
                 INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -408,6 +868,33 @@
         assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
     }
 
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_enabledWithDissimilarProgress() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        // No tick since tick-interval is default 0, which means no tick.
+        // But still re-enable next limit feedback.
+        mProvider.onScrollProgress(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 80);
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
 
     @Test
     public void testScrollLimit_doesNotEnabledWithMotionFromDifferentDeviceId() {
@@ -457,9 +944,65 @@
         assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
     }
 
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentDeviceId() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER));
+        // last snap was for input device #2, so limit for input device #1 not re-enabled.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1,
+                InputDevice.SOURCE_ROTARY_ENCODER,
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
+
+    @Test
+    public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentSource() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+        mProvider.onSnapToItem(
+                INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+        requests.add(new HapticFeedbackRequest(
+                SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN));
+        // last snap was for input source touch screen, so rotary's limit is NOT re-enabled.
+        mProvider.onScrollLimit(
+                INPUT_DEVICE_1,
+                InputDevice.SOURCE_ROTARY_ENCODER,
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+    }
 
     private void assertNoFeedback(TestView view) {
-        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+        for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
             assertFeedbackCount(view, feedback, 0);
         }
     }
@@ -469,7 +1012,7 @@
     }
 
     private void assertOnlyFeedback(TestView view, int expectedFeedback, int expectedCount) {
-        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+        for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
             assertFeedbackCount(view, feedback, (feedback == expectedFeedback) ? expectedCount : 0);
         }
     }
@@ -489,8 +1032,9 @@
                 .thenReturn(enabled);
     }
 
-    private static class TestView extends View  {
+    private static class TestView extends View {
         final Map<Integer, Integer> mFeedbackCount = new HashMap<>();
+        final List<HapticFeedbackRequest> mHapticFeedbackRequests = new ArrayList<>();
 
         TestView(Context context) {
             super(context);
@@ -504,5 +1048,47 @@
             mFeedbackCount.put(feedback, mFeedbackCount.get(feedback) + 1);
             return true;
         }
+
+        @Override
+        public void performHapticFeedbackForInputDevice(int feedback, int inputDeviceId,
+                int inputSource, int flags) {
+            mHapticFeedbackRequests.add(
+                    new HapticFeedbackRequest(feedback, inputDeviceId, inputSource));
+        }
+    }
+
+    private static class HapticFeedbackRequest {
+        // <feedback, inputDeviceId, inputSource>
+        private final int[] mArgs = new int[3];
+
+        private HapticFeedbackRequest(int feedback, int inputDeviceId, int inputSource) {
+            mArgs[0] = feedback;
+            mArgs[1] = inputDeviceId;
+            mArgs[2] = inputSource;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            HapticFeedbackRequest other = (HapticFeedbackRequest) obj;
+            return Arrays.equals(this.mArgs, other.mArgs);
+        }
+
+        @Override
+        public int hashCode() {
+            // Shouldn't depend on hash. Should explicitly match mArgs.
+            return Objects.hash(mArgs[0], mArgs[1], mArgs[2]);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("<feedback=%d; inputDeviceId=%d; inputSource=%d>",
+                    mArgs[0], mArgs[1], mArgs[2]);
+        }
     }
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c5b75ff..b990f24 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
 
 import static android.util.SequenceUtils.getInitSeq;
 import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -501,6 +502,20 @@
         assertThat(result).isFalse();
     }
 
+    @UiThreadTest
+    @Test
+    public void performHapticFeedbackForInputDevice_touchFeedbackDisabled_doNothing() {
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.flags = Display.FLAG_TOUCH_FEEDBACK_DISABLED;
+        Display display = new Display(DisplayManagerGlobal.getInstance(), /* displayId= */
+                0, displayInfo, new DisplayAdjustments());
+        ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
+
+        viewRootImpl.performHapticFeedbackForInputDevice(HapticFeedbackConstants.CONTEXT_CLICK,
+                1 /* inputDeviceId */,  SOURCE_ROTARY_ENCODER /* inputSource */,
+                FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
+    }
+
     /**
      * Test the default values are properly set
      */
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
index 44d10d3..b999df4 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -21,17 +21,121 @@
 import android.graphics.Bitmap
 import android.graphics.BlendMode
 import android.graphics.Color
+import android.graphics.Typeface
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Icon
+import android.graphics.text.LineBreakConfig
+import android.os.LocaleList
+import android.text.Layout
+import android.text.ParcelableSpan
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.TextPaint
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.AccessibilityClickableSpan
+import android.text.style.AccessibilityReplacementSpan
+import android.text.style.AccessibilityURLSpan
+import android.text.style.AlignmentSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.BulletSpan
+import android.text.style.EasyEditSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.LeadingMarginSpan
+import android.text.style.LineBackgroundSpan
+import android.text.style.LineBreakConfigSpan
+import android.text.style.LineHeightSpan
+import android.text.style.LocaleSpan
+import android.text.style.QuoteSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.ScaleXSpan
+import android.text.style.SpellCheckSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.SubscriptSpan
+import android.text.style.SuggestionRangeSpan
+import android.text.style.SuggestionSpan
+import android.text.style.SuperscriptSpan
+import android.text.style.TextAppearanceSpan
+import android.text.style.TtsSpan
+import android.text.style.TypefaceSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
 import android.util.proto.ProtoInputStream
 import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createAbsoluteSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityClickableSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityReplacementSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createAnnotationFromProto
+import android.widget.RemoteViewsSerializers.createBackgroundColorSpanFromProto
+import android.widget.RemoteViewsSerializers.createBulletSpanFromProto
+import android.widget.RemoteViewsSerializers.createCharSequenceFromProto
+import android.widget.RemoteViewsSerializers.createEasyEditSpanFromProto
+import android.widget.RemoteViewsSerializers.createForegroundColorSpanFromProto
 import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.createLeadingMarginSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBackgroundSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBreakConfigSpanFromProto
+import android.widget.RemoteViewsSerializers.createLineHeightSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLocaleSpanFromProto
+import android.widget.RemoteViewsSerializers.createQuoteSpanFromProto
+import android.widget.RemoteViewsSerializers.createRelativeSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createScaleXSpanFromProto
+import android.widget.RemoteViewsSerializers.createStrikethroughSpanFromProto
+import android.widget.RemoteViewsSerializers.createStyleSpanFromProto
+import android.widget.RemoteViewsSerializers.createSubscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionRangeSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuperscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createTextAppearanceSpanFromProto
+import android.widget.RemoteViewsSerializers.createTtsSpanFromProto
+import android.widget.RemoteViewsSerializers.createTypefaceSpanFromProto
+import android.widget.RemoteViewsSerializers.createURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createUnderlineSpanFromProto
+import android.widget.RemoteViewsSerializers.writeAbsoluteSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityClickableSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityReplacementSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeAlignmentSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeAnnotationToProto
+import android.widget.RemoteViewsSerializers.writeBackgroundColorSpanToProto
+import android.widget.RemoteViewsSerializers.writeBulletSpanToProto
+import android.widget.RemoteViewsSerializers.writeCharSequenceToProto
+import android.widget.RemoteViewsSerializers.writeEasyEditSpanToProto
+import android.widget.RemoteViewsSerializers.writeForegroundColorSpanToProto
 import android.widget.RemoteViewsSerializers.writeIconToProto
+import android.widget.RemoteViewsSerializers.writeLeadingMarginSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBackgroundSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBreakConfigSpanToProto
+import android.widget.RemoteViewsSerializers.writeLineHeightSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLocaleSpanToProto
+import android.widget.RemoteViewsSerializers.writeQuoteSpanToProto
+import android.widget.RemoteViewsSerializers.writeRelativeSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeScaleXSpanToProto
+import android.widget.RemoteViewsSerializers.writeStrikethroughSpanToProto
+import android.widget.RemoteViewsSerializers.writeStyleSpanToProto
+import android.widget.RemoteViewsSerializers.writeSubscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionRangeSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuperscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeTextAppearanceSpanToProto
+import android.widget.RemoteViewsSerializers.writeTtsSpanToProto
+import android.widget.RemoteViewsSerializers.writeTypefaceSpanToProto
+import android.widget.RemoteViewsSerializers.writeURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeUnderlineSpanToProto
+import androidx.core.os.persistableBundleOf
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.frameworks.coretests.R
 import com.google.common.truth.Truth.assertThat
 import java.io.ByteArrayOutputStream
+import java.util.Locale
+import kotlin.random.Random
+import kotlin.test.assertIs
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -84,6 +188,511 @@
             }
         }
     }
+
+    @Test
+    fun testWriteToProto() {
+        // This test checks that all of the supported spans are written with their start, end and
+        // flags. Span-specific data is tested in other tests.
+        val string = "0123456789"
+        data class SpanSpec(
+            val span: ParcelableSpan,
+            val start: Int = Random.nextInt(0, string.length),
+            val end: Int = Random.nextInt(start, string.length),
+            val flags: Int = Random.nextInt(0, 256).shl(Spanned.SPAN_USER_SHIFT),
+        )
+
+        val specs = listOf(
+            AbsoluteSizeSpan(0),
+            AccessibilityClickableSpan(0),
+            AccessibilityReplacementSpan(null as String?),
+            AccessibilityURLSpan(URLSpan(null)),
+            AlignmentSpan.Standard(Layout.Alignment.ALIGN_LEFT),
+            android.text.Annotation(null, null),
+            BackgroundColorSpan(0),
+            BulletSpan(0),
+            EasyEditSpan(),
+            ForegroundColorSpan(0),
+            LeadingMarginSpan.Standard(0),
+            LineBackgroundSpan.Standard(0),
+            LineBreakConfigSpan(LineBreakConfig.NONE),
+            LineHeightSpan.Standard(1),
+            LocaleSpan(LocaleList.getDefault()),
+            QuoteSpan(),
+            RelativeSizeSpan(0f),
+            ScaleXSpan(0f),
+            SpellCheckSpan(),
+            StrikethroughSpan(),
+            StyleSpan(0),
+            SubscriptSpan(),
+            SuggestionRangeSpan(),
+            SuggestionSpan(context, arrayOf(), 0),
+            SuperscriptSpan(),
+            TextAppearanceSpan(context, android.R.style.TextAppearance),
+            TtsSpan(null, persistableBundleOf()),
+            TypefaceSpan(null),
+            UnderlineSpan(),
+            URLSpan(null),
+        ).map { SpanSpec(it) }
+
+        val original = SpannableStringBuilder(string)
+        for (spec in specs) {
+            original.setSpan(spec.span, spec.start, spec.end, spec.flags)
+        }
+
+        val out = ProtoOutputStream()
+        writeCharSequenceToProto(out, original)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createCharSequenceFromProto(input)
+
+        assertIs<Spanned>(copy)
+        for (spec in specs) {
+            val spans = copy.getSpans(spec.start, spec.end, Object::class.java)
+            android.util.Log.e("TestRunner", "Can I find $spec")
+            val span = spans.single { spec.span::class.java.name == it::class.java.name }
+            assertEquals(spec.flags, copy.getSpanFlags(span))
+        }
+    }
+
+    @Test
+    fun writeToProto_notSpanned() {
+        val string = "Hello World"
+        val out = ProtoOutputStream()
+        writeCharSequenceToProto(out, string)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createCharSequenceFromProto(input)
+        assertIs<String>(copy)
+        assertEquals(copy, string)
+    }
+
+    @Test
+    fun testAbsoluteSizeSpan() {
+        for (span in arrayOf(AbsoluteSizeSpan(0, false), AbsoluteSizeSpan(2, true))) {
+            val out = ProtoOutputStream()
+            writeAbsoluteSizeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAbsoluteSizeSpanFromProto(input)
+            assertEquals(span.size, copy.size)
+            assertEquals(span.dip, copy.dip)
+        }
+    }
+
+    @Test
+    fun testAccessibilityClickableSpan() {
+        for (id in 0..1) {
+            val span = AccessibilityClickableSpan(id)
+            val out = ProtoOutputStream()
+            writeAccessibilityClickableSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityClickableSpanFromProto(input)
+            assertEquals(span.originalClickableSpanId, copy.originalClickableSpanId)
+        }
+    }
+
+    @Test
+    fun testAccessibilityReplacementSpan() {
+        for (contentDescription in arrayOf(null, "123")) {
+            val span = AccessibilityReplacementSpan(contentDescription)
+            val out = ProtoOutputStream()
+            writeAccessibilityReplacementSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityReplacementSpanFromProto(input)
+            assertEquals(span.contentDescription, copy.contentDescription)
+        }
+    }
+
+    @Test
+    fun testAccessibilityURLSpan() {
+        for (url in arrayOf(null, "123")) {
+            val span = AccessibilityURLSpan(URLSpan(url))
+            val out = ProtoOutputStream()
+            writeAccessibilityURLSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAccessibilityURLSpanFromProto(input)
+            assertEquals(span.url, copy.url)
+        }
+    }
+
+    @Test
+    fun testAlignmentSpanStandard() {
+        for (alignment in arrayOf(
+            Layout.Alignment.ALIGN_CENTER,
+            Layout.Alignment.ALIGN_LEFT,
+            Layout.Alignment.ALIGN_NORMAL,
+            Layout.Alignment.ALIGN_OPPOSITE)) {
+            val span = AlignmentSpan.Standard(alignment)
+            val out = ProtoOutputStream()
+            writeAlignmentSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = RemoteViewsSerializers.createAlignmentSpanStandardFromProto(input)
+            assertEquals(span.alignment, copy.alignment)
+        }
+    }
+
+    @Test
+    fun testAnnotation() {
+        for ((key, value) in arrayOf(null to null, "key" to "value")) {
+            val span = android.text.Annotation(key, value)
+            val out = ProtoOutputStream()
+            writeAnnotationToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createAnnotationFromProto(input)
+            assertEquals(span.key, copy.key)
+            assertEquals(span.value, copy.value)
+        }
+    }
+
+    @Test
+    fun testBackgroundColorSpan() {
+        for (color in intArrayOf(Color.RED, Color.MAGENTA)) {
+            val span = BackgroundColorSpan(color)
+            val out = ProtoOutputStream()
+            writeBackgroundColorSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createBackgroundColorSpanFromProto(input)
+            assertEquals(span.backgroundColor, copy.backgroundColor)
+        }
+    }
+
+    @Test
+    fun testBulletSpan() {
+        for (span in arrayOf(BulletSpan(), BulletSpan(2, Color.RED, 5))) {
+            val out = ProtoOutputStream()
+            writeBulletSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createBulletSpanFromProto(input)
+            assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+            assertEquals(span.color, copy.color)
+            assertEquals(span.color, copy.color)
+            assertEquals(span.gapWidth, copy.gapWidth)
+        }
+    }
+
+    @Test
+    fun testEasyEditSpan() {
+        val span = EasyEditSpan()
+        val out = ProtoOutputStream()
+        writeEasyEditSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createEasyEditSpanFromProto(input)
+    }
+
+    @Test
+    fun testForegroundColorSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = ForegroundColorSpan(color)
+            val out = ProtoOutputStream()
+            writeForegroundColorSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createForegroundColorSpanFromProto(input)
+            assertEquals(span.foregroundColor.toLong(), copy.foregroundColor.toLong())
+        }
+    }
+
+    @Test
+    fun testLeadingMarginSpanStandard() {
+        for (span in arrayOf(LeadingMarginSpan.Standard(10, 20), LeadingMarginSpan.Standard(0))) {
+            val out = ProtoOutputStream()
+            writeLeadingMarginSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLeadingMarginSpanStandardFromProto(input)
+            assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+            assertEquals(span.getLeadingMargin(false), copy.getLeadingMargin(false))
+        }
+    }
+
+    @Test
+    fun testLineBackgroundSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = LineBackgroundSpan.Standard(color)
+            val out = ProtoOutputStream()
+            writeLineBackgroundSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLineBackgroundSpanStandardFromProto(input)
+            assertEquals(span.color, copy.color)
+        }
+    }
+
+    @Test
+    fun testLineBreakConfigSpan() {
+        val config = LineBreakConfig.Builder()
+            .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO)
+            .setHyphenation(LineBreakConfig.HYPHENATION_ENABLED)
+            .build()
+        val span = LineBreakConfigSpan(config)
+        val out = ProtoOutputStream()
+        writeLineBreakConfigSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createLineBreakConfigSpanFromProto(input).lineBreakConfig
+        assertEquals(copy.lineBreakStyle, config.lineBreakStyle)
+        assertEquals(copy.lineBreakWordStyle, config.lineBreakWordStyle)
+        assertEquals(copy.hyphenation, config.hyphenation)
+    }
+
+    @Test
+    fun testLineHeightSpanStandard() {
+        for (height in 1..2) {
+            val span = LineHeightSpan.Standard(height)
+            val out = ProtoOutputStream()
+            writeLineHeightSpanStandardToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLineHeightSpanStandardFromProto(input)
+            assertEquals(span.height, copy.height)
+        }
+    }
+
+    @Test
+    fun testLocaleSpan() {
+        for (list in arrayOf(
+            LocaleList.getEmptyLocaleList(),
+            LocaleList.forLanguageTags("en"),
+            LocaleList.forLanguageTags("en-GB,en"),
+        )) {
+            val span = LocaleSpan(list)
+            val out = ProtoOutputStream()
+            writeLocaleSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createLocaleSpanFromProto(input)
+            assertEquals(span.locales[0], copy.locale)
+            assertEquals(span.locales, copy.locales)
+        }
+    }
+
+    @Test
+    fun testQuoteSpan() {
+        for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+            val span = QuoteSpan(color)
+            val out = ProtoOutputStream()
+            writeQuoteSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createQuoteSpanFromProto(input)
+            assertEquals(span.color, copy.color)
+            assertTrue(span.gapWidth > 0)
+            assertTrue(span.stripeWidth > 0)
+        }
+    }
+
+    @Test
+    fun testRelativeSizeSpan() {
+        for (size in arrayOf(0f, 1.0f)) {
+            val span = RelativeSizeSpan(size)
+            val out = ProtoOutputStream()
+            writeRelativeSizeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createRelativeSizeSpanFromProto(input)
+            assertEquals(span.sizeChange, copy.sizeChange)
+        }
+    }
+
+    @Test
+    fun testScaleXSpan() {
+        for (scale in arrayOf(0f, 1.0f)) {
+            val span = ScaleXSpan(scale)
+            val out = ProtoOutputStream()
+            writeScaleXSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createScaleXSpanFromProto(input)
+            assertEquals(span.scaleX, copy.scaleX, 0.0f)
+        }
+    }
+
+    @Test
+    fun testStrikethroughSpan() {
+        val span = StrikethroughSpan()
+        val out = ProtoOutputStream()
+        writeStrikethroughSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createStrikethroughSpanFromProto(input)
+    }
+
+    @Test
+    fun testStyleSpan() {
+        for (style in arrayOf(Typeface.BOLD, Typeface.NORMAL)) {
+            val span = StyleSpan(style)
+            val out = ProtoOutputStream()
+            writeStyleSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createStyleSpanFromProto(input)
+            assertEquals(span.style, copy.style)
+        }
+    }
+
+    @Test
+    fun testSubscriptSpan() {
+        val span = SubscriptSpan()
+        val out = ProtoOutputStream()
+        writeSubscriptSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createSubscriptSpanFromProto(input)
+    }
+
+    @Test
+    fun testSuggestionSpan() {
+        val suggestions = arrayOf("suggestion1", "suggestion2")
+        val span = SuggestionSpan(
+            Locale.forLanguageTag("en"), suggestions,
+            SuggestionSpan.FLAG_AUTO_CORRECTION)
+
+        val out = ProtoOutputStream()
+        writeSuggestionSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createSuggestionSpanFromProto(input)
+        assertArrayEquals("Should (de)serialize suggestions",
+            suggestions, copy.suggestions)
+    }
+
+    @Test
+    fun testSuggestionRangeSpan() {
+        for (backgroundColor in 0..1) {
+            val span = SuggestionRangeSpan()
+            span.backgroundColor = backgroundColor
+            val out = ProtoOutputStream()
+            writeSuggestionRangeSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createSuggestionRangeSpanFromProto(input)
+            assertEquals(span.backgroundColor, copy.backgroundColor)
+        }
+    }
+
+    @Test
+    fun testSuperscriptSpan() {
+        val span = SuperscriptSpan()
+        val out = ProtoOutputStream()
+        writeSuperscriptSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createSuperscriptSpanFromProto(input)
+    }
+
+
+    @Test
+    fun testTextAppearanceSpan_FontResource() {
+        val span = TextAppearanceSpan(context, R.style.customFont)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val tp = TextPaint()
+        span.updateDrawState(tp)
+        val originalSpanTextWidth = tp.measureText("a")
+        copy.updateDrawState(tp)
+        assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+    }
+
+    @Test
+    fun testTextAppearanceSpan_FontResource_WithStyle() {
+        val span = TextAppearanceSpan(context, R.style.customFontWithStyle)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val tp = TextPaint()
+        span.updateDrawState(tp)
+        val originalSpanTextWidth = tp.measureText("a")
+        copy.updateDrawState(tp)
+        assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+    }
+
+    @Test
+    fun testTextAppearanceSpan_WithAllAttributes() {
+        val span = TextAppearanceSpan(context, R.style.textAppearanceWithAllAttributes)
+        val out = ProtoOutputStream()
+        writeTextAppearanceSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTextAppearanceSpanFromProto(input)
+        val originalTextColor = span.textColor
+        val copyTextColor = copy.textColor
+        val originalLinkTextColor = span.linkTextColor
+        val copyLinkTextColor = copy.linkTextColor
+        assertEquals(span.family, copy.family)
+        // ColorStateList doesn't implement equals(), so we borrow this code
+        // from ColorStateListTest.java to test correctness of parceling.
+        assertEquals(originalTextColor.isStateful, copyTextColor.isStateful)
+        assertEquals(originalTextColor.defaultColor, copyTextColor.defaultColor)
+        assertEquals(originalLinkTextColor.isStateful,
+            copyLinkTextColor.isStateful)
+        assertEquals(originalLinkTextColor.defaultColor,
+            copyLinkTextColor.defaultColor)
+        assertEquals(span.textSize.toLong(), copy.textSize.toLong())
+        assertEquals(span.textStyle.toLong(), copy.textStyle.toLong())
+        assertEquals(span.textFontWeight.toLong(), copy.textFontWeight.toLong())
+        assertEquals(span.textLocales, copy.textLocales)
+        assertEquals(span.shadowColor.toLong(), copy.shadowColor.toLong())
+        assertEquals(span.shadowDx, copy.shadowDx, 0.0f)
+        assertEquals(span.shadowDy, copy.shadowDy, 0.0f)
+        assertEquals(span.shadowRadius, copy.shadowRadius, 0.0f)
+        assertEquals(span.fontFeatureSettings, copy.fontFeatureSettings)
+        assertEquals(span.fontVariationSettings, copy.fontVariationSettings)
+        assertEquals(span.isElegantTextHeight, copy.isElegantTextHeight)
+        assertEquals(span.letterSpacing, copy.letterSpacing, 0f)
+        // typeface is omitted from TextAppearanceSpan proto
+    }
+
+    @Test
+    fun testTtsSpan() {
+        val bundle = persistableBundleOf(
+            "argument.one" to "value.one",
+            "argument.two" to "value.two",
+            "argument.three" to 3L,
+            "argument.four" to 4L,
+        )
+        val span = TtsSpan("test.type.five", bundle)
+        val out = ProtoOutputStream()
+        writeTtsSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTtsSpanFromProto(input)
+        assertEquals("test.type.five", copy.type)
+        val args = copy.args
+        assertEquals(4, args.size())
+        assertEquals("value.one", args.getString("argument.one"))
+        assertEquals("value.two", args.getString("argument.two"))
+        assertEquals(3, args.getLong("argument.three"))
+        assertEquals(4, args.getLong("argument.four"))
+    }
+
+
+    @Test
+    fun testTtsSpan_null() {
+        val span = TtsSpan(null, null)
+        val out = ProtoOutputStream()
+        writeTtsSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        val copy = createTtsSpanFromProto(input)
+        assertNull(copy.type)
+        assertNull(copy.args)
+    }
+
+    @Test
+    fun testTypefaceSpan() {
+        for (family in arrayOf(null, "monospace")) {
+            val span = TypefaceSpan(family)
+            val out = ProtoOutputStream()
+            writeTypefaceSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createTypefaceSpanFromProto(input)
+            assertEquals(span.family, copy.family)
+        }
+    }
+
+    @Test
+    fun testUnderlineSpan() {
+        val span = UnderlineSpan()
+        val out = ProtoOutputStream()
+        writeUnderlineSpanToProto(out, span)
+        val input = ProtoInputStream(out.bytes)
+        createUnderlineSpanFromProto(input)
+    }
+
+    @Test
+    fun testURLSpan() {
+        for (url in arrayOf(null, "content://url")) {
+            val span = URLSpan(url)
+            val out = ProtoOutputStream()
+            writeURLSpanToProto(out, span)
+            val input = ProtoInputStream(out.bytes)
+            val copy = createURLSpanFromProto(input)
+            assertEquals(span.url, copy.url)
+        }
+    }
 }
 
 fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 6c8dcd3..fdc00ba 100644
--- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -93,7 +93,8 @@
                 ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
                 Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
                 false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
-                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
+                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */,
+                0 /* uiMode */);
     }
 
     private static TaskDescription createTaskDescription(int background,
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index fc233fb..3b9f35b 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -161,15 +161,15 @@
     }
 
     @Test
-    public void testAppendBoolean() throws Exception {
+    public void testAppendBooleanDuplicatesAllowed() throws Exception {
         assertArrayEquals(new boolean[] { true },
-                ArrayUtils.appendBoolean(null, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(null, true));
         assertArrayEquals(new boolean[] { true },
-                ArrayUtils.appendBoolean(new boolean[] { }, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { }, true));
         assertArrayEquals(new boolean[] { true, false },
-                ArrayUtils.appendBoolean(new boolean[] { true }, false));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, false));
         assertArrayEquals(new boolean[] { true, true },
-                ArrayUtils.appendBoolean(new boolean[] { true }, true));
+                ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, true));
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 7be14724..25e7107 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2725,15 +2725,19 @@
     TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
             @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
             @NonNull Intent intent, @NonNull Activity launchActivity) {
+        final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
         if (isActivityFromSplit(launchActivity)) {
             // We restrict to launch the overlay from split. Fallback to treat it as normal
             // launch.
+            Log.w(TAG, "It's not allowed to launch overlay container with tag=" + overlayTag
+                    + " from activity in Activity Embedding split."
+                    + " Launching activity=" + launchActivity
+                    + " Fallback to launch the activity as normal launch.");
             return null;
         }
 
         final List<TaskFragmentContainer> overlayContainers =
                 getAllNonFinishingOverlayContainers();
-        final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
         final boolean associateLaunchingActivity = options
                 .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
 
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
index ddcd5c6..e3217811 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -16,6 +16,7 @@
   -->
 <com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
@@ -35,7 +36,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="16dp"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnSurface"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
 </com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index 1cbd0e6..f1ecde4 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -17,6 +17,7 @@
 <com.android.wm.shell.bubbles.bar.BubbleBarMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center_horizontal"
@@ -51,7 +52,7 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
             android:layout_weight="1"
-            android:textColor="?android:attr/textColorPrimary"
+            android:textColor="?androidprv:attr/materialColorOnSurface"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
         <ImageView
@@ -61,7 +62,7 @@
             android:layout_marginStart="8dp"
             android:contentDescription="@null"
             android:src="@drawable/ic_expand_less"
-            app:tint="?android:attr/textColorPrimary" />
+            app:tint="?androidprv:attr/materialColorOnSurface" />
 
     </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dc022b4..9027bf3 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -59,7 +60,8 @@
     public static boolean isOpeningType(@WindowManager.TransitionType int type) {
         return type == TRANSIT_OPEN
                 || type == TRANSIT_TO_FRONT
-                || type == TRANSIT_KEYGUARD_GOING_AWAY;
+                || type == TRANSIT_KEYGUARD_GOING_AWAY
+                || type == TRANSIT_PREPARE_BACK_NAVIGATION;
     }
 
     /** @return true if the transition was triggered by closing something vs opening something */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f14f419..7275c64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,9 +16,14 @@
 
 package com.android.wm.shell.back;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
@@ -31,6 +36,8 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -837,8 +844,9 @@
         mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
 
         // The next callback should be {@link #onBackAnimationFinished}.
+        final boolean migrateBackToTransition = migratePredictiveBackTransition();
         if (mCurrentTracker.getTriggerBack()) {
-            if (migratePredictiveBackTransition()) {
+            if (migrateBackToTransition) {
                 // notify core gesture is commit
                 if (shouldTriggerCloseTransition()) {
                     mBackTransitionHandler.mCloseTransitionRequested = true;
@@ -856,6 +864,10 @@
             // start post animation
             dispatchOnBackInvoked(mActiveCallback);
         } else {
+            if (migrateBackToTransition
+                    && mBackTransitionHandler.mPrepareOpenTransition != null) {
+                mBackTransitionHandler.createClosePrepareTransition();
+            }
             tryDispatchOnBackCancelled(mActiveCallback);
         }
     }
@@ -960,6 +972,7 @@
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
         cancelLatencyTracking();
         mReceivedNullNavigationInfo = false;
+        mBackTransitionHandler.mLastTrigger = triggerBack;
         if (mBackNavigationInfo != null) {
             mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1128,12 +1141,18 @@
 
         Runnable mOnAnimationFinishCallback;
         boolean mCloseTransitionRequested;
-        boolean mOpeningRunning;
         SurfaceControl.Transaction mFinishOpenTransaction;
         Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
         QueuedTransition mQueuedTransition = null;
+        boolean mLastTrigger;
+        // The Transition to make behindActivity become visible
+        IBinder mPrepareOpenTransition;
+        // The Transition to make behindActivity become invisible, if prepare open exist and
+        // animation is canceled, start a close prepare transition to finish the whole transition.
+        IBinder mClosePrepareTransition;
+        TransitionInfo mOpenTransitionInfo;
         void onAnimationFinished() {
-            if (!mCloseTransitionRequested) {
+            if (!mCloseTransitionRequested && mClosePrepareTransition == null) {
                 applyFinishOpenTransition();
             }
             if (mOnAnimationFinishCallback != null) {
@@ -1158,7 +1177,8 @@
                 mFinishOpenTransitionCallback.onTransitionFinished(null);
                 mFinishOpenTransitionCallback = null;
             }
-            mOpeningRunning = false;
+            mOpenTransitionInfo = null;
+            mPrepareOpenTransition = null;
         }
 
         private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
@@ -1178,21 +1198,42 @@
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
             // need to post to ShellExecutor when called.
+            if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+                // only consume it if this transition hasn't being processed.
+                if (mClosePrepareTransition != null) {
+                    mClosePrepareTransition = null;
+                    applyAndFinish(st, ft, finishCallback);
+                    return true;
+                }
+                return false;
+            }
+
             if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
                     && !isGestureBackTransition(info)) {
                 return false;
             }
+
+            if (shouldCancelAnimation(info)) {
+                return false;
+            }
+
             if (mApps == null || mApps.length == 0) {
                 if (mBackNavigationInfo != null && mShellBackAnimationRegistry
                         .isWaitingAnimation(mBackNavigationInfo.getType())) {
                     // Waiting for animation? Queue update to wait for animation start.
                     consumeQueuedTransitionIfNeeded();
                     mQueuedTransition = new QueuedTransition(info, st, ft, finishCallback);
-                } else {
+                    return true;
+                } else if (mLastTrigger) {
                     // animation was done, consume directly
                     applyAndFinish(st, ft, finishCallback);
+                    return true;
+                } else {
+                    // animation was cancelled but transition haven't happen, we must handle it
+                    if (mClosePrepareTransition == null && mCurrentTracker.isFinished()) {
+                        createClosePrepareTransition();
+                    }
                 }
-                return true;
             }
 
             if (handlePrepareTransition(info, st, ft, finishCallback)) {
@@ -1201,12 +1242,131 @@
             return handleCloseTransition(info, st, ft, finishCallback);
         }
 
+        void createClosePrepareTransition() {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.restoreBackNavi();
+            mClosePrepareTransition = mTransitions.startTransition(
+                    TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, wct, mBackTransitionHandler);
+        }
+        private void mergePendingTransitions(TransitionInfo info) {
+            if (mOpenTransitionInfo == null) {
+                return;
+            }
+            // Copy initial changes to final transition
+            final TransitionInfo init = mOpenTransitionInfo;
+            // find prepare open target
+            boolean openShowWallpaper = false;
+            ComponentName openComponent = null;
+            int tmpSize;
+            int openTaskId = INVALID_TASK_ID;
+            for (int j = init.getChanges().size() - 1; j >= 0; --j) {
+                final TransitionInfo.Change change = init.getChanges().get(j);
+                if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                    openComponent = findComponentName(change);
+                    openTaskId = findTaskId(change);
+                    if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
+                        openShowWallpaper = true;
+                    }
+                    break;
+                }
+            }
+            if (openComponent == null && openTaskId == INVALID_TASK_ID) {
+                // shouldn't happen.
+                return;
+            }
+            // find first non-prepare open target
+            boolean isOpen = false;
+            tmpSize = info.getChanges().size();
+            for (int j = 0; j < tmpSize; ++j) {
+                final TransitionInfo.Change change = info.getChanges().get(j);
+                final ComponentName firstNonOpen = findComponentName(change);
+                final int firstTaskId = findTaskId(change);
+                if ((firstNonOpen != null && firstNonOpen != openComponent)
+                        || (firstTaskId != INVALID_TASK_ID && firstTaskId != openTaskId)) {
+                    // this is original close target, potential be close, but cannot determine from
+                    // it
+                    if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                        isOpen = !TransitionUtil.isClosingMode(change.getMode());
+                    } else {
+                        isOpen = TransitionUtil.isOpeningMode(change.getMode());
+                        break;
+                    }
+                }
+            }
+
+            if (!isOpen) {
+                // Close transition, the transition info should be:
+                // init info(open A & wallpaper)
+                // current info(close B target)
+                // remove init info(open/change A target & wallpaper)
+                boolean moveToTop = false;
+                for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+                    final TransitionInfo.Change change = info.getChanges().get(j);
+                    if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                        moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
+                        info.getChanges().remove(j);
+                    } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
+                            || !change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                        info.getChanges().remove(j);
+                    }
+                }
+                tmpSize = info.getChanges().size();
+                for (int i = 0; i < tmpSize; ++i) {
+                    final TransitionInfo.Change change = init.getChanges().get(i);
+                    if (moveToTop) {
+                        if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                            change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
+                        }
+                    }
+                    info.getChanges().add(i, change);
+                }
+            } else {
+                // Open transition, the transition info should be:
+                // init info(open A & wallpaper)
+                // current info(open C target + close B target + close A & wallpaper)
+
+                // If close target isn't back navigated, filter out close A & wallpaper because the
+                // (open C + close B) pair didn't participant prepare close
+                boolean nonBackOpen = false;
+                boolean nonBackClose = false;
+                tmpSize = info.getChanges().size();
+                for (int j = 0; j < tmpSize; ++j) {
+                    final TransitionInfo.Change change = info.getChanges().get(j);
+                    if (!change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+                            && canBeTransitionTarget(change)) {
+                        final int mode = change.getMode();
+                        nonBackOpen |= TransitionUtil.isOpeningMode(mode);
+                        nonBackClose |= TransitionUtil.isClosingMode(mode);
+                    }
+                }
+                if (nonBackClose && nonBackOpen) {
+                    for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+                        final TransitionInfo.Change change = info.getChanges().get(j);
+                        if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                            info.getChanges().remove(j);
+                        } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
+                            info.getChanges().remove(j);
+                        }
+                    }
+                }
+            }
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
+                    + "transitions result=%s", info);
+        }
+
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (!isGestureBackTransition(info)) {
-                if (mOpeningRunning) {
+            if (mClosePrepareTransition == transition) {
+                mClosePrepareTransition = null;
+            }
+            // try to handle unexpected transition
+            mergePendingTransitions(info);
+
+            if (!isGestureBackTransition(info) || shouldCancelAnimation(info)
+                    || !mCloseTransitionRequested) {
+                if (mPrepareOpenTransition != null) {
                     applyFinishOpenTransition();
                 }
                 if (mQueuedTransition != null) {
@@ -1222,7 +1382,7 @@
                         // animation was done
                         applyFinishOpenTransition();
                         mCloseTransitionRequested = false;
-                    } // else, let queued transition to play
+                    } // let queued transition finish.
                 } else {
                     // we are animating, wait until animation finish
                     mOnAnimationFinishCallback = () -> {
@@ -1233,6 +1393,56 @@
             }
         }
 
+        // Cancel close animation if something happen unexpected, let another handler to handle
+        private boolean shouldCancelAnimation(@NonNull TransitionInfo info) {
+            final boolean noCloseAllowed =
+                    info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
+            boolean unableToHandle = false;
+            boolean filterTargets = false;
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change c = info.getChanges().get(i);
+                final boolean backGestureAnimated = c.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
+                if (!backGestureAnimated && !c.hasFlags(FLAG_IS_WALLPAPER)) {
+                    // something we cannot handle?
+                    unableToHandle = true;
+                    filterTargets = true;
+                } else if (noCloseAllowed && backGestureAnimated
+                        && TransitionUtil.isClosingMode(c.getMode())) {
+                    // Prepare back navigation shouldn't contain close change, unless top app
+                    // request close.
+                    unableToHandle = true;
+                }
+            }
+            if (!unableToHandle) {
+                return false;
+            }
+            if (!filterTargets) {
+                return true;
+            }
+            if (TransitionUtil.isOpeningType(info.getType())
+                    || TransitionUtil.isClosingType(info.getType())) {
+                boolean removeWallpaper = false;
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    final TransitionInfo.Change c = info.getChanges().get(i);
+                    // filter out opening target, keep original closing target in this transition
+                    if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+                            && TransitionUtil.isOpeningMode(c.getMode())) {
+                        info.getChanges().remove(i);
+                        removeWallpaper |= c.hasFlags(FLAG_SHOW_WALLPAPER);
+                    }
+                }
+                if (removeWallpaper) {
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change c = info.getChanges().get(i);
+                        if (c.hasFlags(FLAG_IS_WALLPAPER)) {
+                            info.getChanges().remove(i);
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+
         /**
          * Check whether this transition is prepare for predictive back animation, which could
          * happen when core make an activity become visible.
@@ -1247,9 +1457,11 @@
             }
 
             SurfaceControl openingLeash = null;
-            for (int i = mApps.length - 1; i >= 0; --i) {
-                if (mApps[i].mode == MODE_OPENING) {
-                    openingLeash = mApps[i].leash;
+            if (mApps != null) {
+                for (int i = mApps.length - 1; i >= 0; --i) {
+                    if (mApps[i].mode == MODE_OPENING) {
+                        openingLeash = mApps[i].leash;
+                    }
                 }
             }
             if (openingLeash != null) {
@@ -1259,13 +1471,14 @@
                         final Point offset = c.getEndRelOffset();
                         st.setPosition(c.getLeash(), offset.x, offset.y);
                         st.reparent(c.getLeash(), openingLeash);
+                        st.setAlpha(c.getLeash(), 1.0f);
                     }
                 }
             }
             st.apply();
             mFinishOpenTransaction = ft;
             mFinishOpenTransitionCallback = finishCallback;
-            mOpeningRunning = true;
+            mOpenTransitionInfo = info;
             return true;
         }
 
@@ -1288,6 +1501,10 @@
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
+            if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
+                    || !mCloseTransitionRequested) {
+                return false;
+            }
             SurfaceControl openingLeash = null;
             SurfaceControl closingLeash = null;
             for (int i = mApps.length - 1; i >= 0; --i) {
@@ -1325,7 +1542,12 @@
         public WindowContainerTransaction handleRequest(
                 @NonNull IBinder transition,
                 @NonNull TransitionRequestInfo request) {
-            if (request.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+            final int type = request.getType();
+            if (type == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+                mPrepareOpenTransition = transition;
+                return new WindowContainerTransaction();
+            }
+            if (type == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
                 return new WindowContainerTransaction();
             }
             if (TransitionUtil.isClosingType(request.getType()) && mCloseTransitionRequested) {
@@ -1369,4 +1591,36 @@
             }
         }
     }
+
+    private static ComponentName findComponentName(TransitionInfo.Change change) {
+        final ComponentName componentName = change.getActivityComponent();
+        if (componentName != null) {
+            return componentName;
+        }
+        final TaskInfo taskInfo = change.getTaskInfo();
+        if (taskInfo != null) {
+            return taskInfo.topActivity;
+        }
+        return null;
+    }
+
+    private static int findTaskId(TransitionInfo.Change change) {
+        final TaskInfo taskInfo = change.getTaskInfo();
+        if (taskInfo != null) {
+            return taskInfo.taskId;
+        }
+        return INVALID_TASK_ID;
+    }
+
+    private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
+            TransitionInfo.Change change) {
+        final ComponentName openChange = findComponentName(change);
+        final int firstTaskId = findTaskId(change);
+        return (openChange != null && openChange == topActivity)
+                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId);
+    }
+
+    private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
+        return findComponentName(change) != null || findTaskId(change) != INVALID_TASK_ID;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 00b9777..1c71ef4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -64,7 +64,7 @@
     void update(Icon icon, String title, @ColorInt int tint) {
         if (tint == Color.TRANSPARENT) {
             final TypedArray typedArray = getContext().obtainStyledAttributes(
-                    new int[]{android.R.attr.textColorPrimary});
+                    new int[]{com.android.internal.R.attr.materialColorOnSurface});
             mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
         } else {
             icon.setTint(tint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index d5f4924..8389c81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -17,6 +17,7 @@
 
 import android.annotation.ColorInt;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
@@ -27,6 +28,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.core.widget.ImageViewCompat;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -39,6 +42,7 @@
     private ViewGroup mBubbleSectionView;
     private ViewGroup mActionsSectionView;
     private ImageView mBubbleIconView;
+    private ImageView mBubbleDismissIconView;
     private TextView mBubbleTitleView;
 
     public BubbleBarMenuView(Context context) {
@@ -65,13 +69,18 @@
         mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
         mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
         mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
-        updateActionsBackgroundColor();
+        mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
+        updateThemeColors();
     }
 
-    private void updateActionsBackgroundColor() {
+    private void updateThemeColors() {
         try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
-                com.android.internal.R.attr.materialColorSurfaceBright})) {
+                com.android.internal.R.attr.materialColorSurfaceBright,
+                com.android.internal.R.attr.materialColorOnSurface
+        })) {
             mActionsSectionView.getBackground().setTint(ta.getColor(0, Color.WHITE));
+            ImageViewCompat.setImageTintList(mBubbleDismissIconView,
+                    ColorStateList.valueOf(ta.getColor(1, Color.BLACK)));
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 02918db..0d72998 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -19,12 +19,13 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.core.content.ContextCompat;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
@@ -172,12 +173,17 @@
     private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
         ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
         Resources resources = mContext.getResources();
-
+        int tintColor;
+        try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+                com.android.internal.R.attr.materialColorOnSurface})) {
+            tintColor = ta.getColor(0, Color.TRANSPARENT);
+        }
         if (bubble.isConversation()) {
             // Don't bubble conversation action
             menuActions.add(new BubbleBarMenuView.MenuAction(
                     Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
                     resources.getString(R.string.bubbles_dont_bubble_conversation),
+                    tintColor,
                     view -> {
                         hideMenu(true /* animated */);
                         if (mListener != null) {
@@ -204,7 +210,7 @@
         menuActions.add(new BubbleBarMenuView.MenuAction(
                 Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
                 resources.getString(R.string.bubble_dismiss_text),
-                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+                tintColor,
                 view -> {
                     hideMenu(true /* animated */);
                     if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 1fb0e17..c4c177c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -47,6 +47,8 @@
     private final SparseArray<PerDisplay> mInsetsPerDisplay = new SparseArray<>();
     private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
             new SparseArray<>();
+    private final CopyOnWriteArrayList<OnInsetsChangedListener> mGlobalListeners =
+            new CopyOnWriteArrayList<>();
 
     public DisplayInsetsController(IWindowManager wmService,
             ShellInit shellInit,
@@ -81,6 +83,16 @@
     }
 
     /**
+     * Adds a callback to listen for insets changes for any display. Note that the
+     * listener will not be updated with the existing state of the insets on any display.
+     */
+    public void addGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+        if (!mGlobalListeners.contains(listener)) {
+            mGlobalListeners.add(listener);
+        }
+    }
+
+    /**
      * Removes a callback listening for insets changes from a particular display.
      */
     public void removeInsetsChangedListener(int displayId, OnInsetsChangedListener listener) {
@@ -91,6 +103,13 @@
         listeners.remove(listener);
     }
 
+    /**
+     * Removes a callback listening for insets changes from any display.
+     */
+    public void removeGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+        mGlobalListeners.remove(listener);
+    }
+
     @Override
     public void onDisplayAdded(int displayId) {
         PerDisplay pd = new PerDisplay(displayId);
@@ -138,12 +157,17 @@
 
         private void insetsChanged(InsetsState insetsState) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
-            if (listeners == null) {
+            if (listeners == null && mGlobalListeners.isEmpty()) {
                 return;
             }
             mDisplayController.updateDisplayInsets(mDisplayId, insetsState);
-            for (OnInsetsChangedListener listener : listeners) {
-                listener.insetsChanged(insetsState);
+            for (OnInsetsChangedListener listener : mGlobalListeners) {
+                listener.insetsChanged(mDisplayId, insetsState);
+            }
+            if (listeners != null) {
+                for (OnInsetsChangedListener listener : listeners) {
+                    listener.insetsChanged(mDisplayId, insetsState);
+                }
             }
         }
 
@@ -285,6 +309,13 @@
         default void insetsChanged(InsetsState insetsState) {}
 
         /**
+         * Called when the window insets configuration has changed for the given display.
+         */
+        default void insetsChanged(int displayId, InsetsState insetsState) {
+            insetsChanged(insetsState);
+        }
+
+        /**
          * Called when this window retrieved control over a specified set of insets sources.
          */
         default void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 19a109e..e2988bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -23,7 +23,10 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
+import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
 import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
+import static com.android.wm.shell.common.split.SplitScreenConstants.VEIL_DELAY_DURATION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,7 @@
     private final SurfaceSession mSurfaceSession;
 
     private Drawable mIcon;
-    private ImageView mResizingIconView;
+    private ImageView mVeilIconView;
     private SurfaceControlViewHost mViewHost;
     private SurfaceControl mHostLeash;
     private SurfaceControl mIconLeash;
@@ -83,13 +86,14 @@
     private SurfaceControl mScreenshot;
 
     private boolean mShown;
-    private boolean mIsResizing;
+    /** True if the task is going through some kind of transition (moving or changing size). */
+    private boolean mIsCurrentlyChanging;
     /** The original bounds of the main task, captured at the beginning of a resize transition. */
     private final Rect mOldMainBounds = new Rect();
     /** The original bounds of the side task, captured at the beginning of a resize transition. */
     private final Rect mOldSideBounds = new Rect();
     /** The current bounds of the main task, mid-resize. */
-    private final Rect mResizingBounds = new Rect();
+    private final Rect mInstantaneousBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
     private ValueAnimator mScreenshotAnimator;
@@ -134,7 +138,7 @@
         mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
         final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
                 .inflate(R.layout.split_decor, null);
-        mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+        mVeilIconView = rootLayout.findViewById(R.id.split_resizing_icon);
 
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
@@ -191,28 +195,28 @@
         }
         mHostLeash = null;
         mIcon = null;
-        mResizingIconView = null;
-        mIsResizing = false;
+        mVeilIconView = null;
+        mIsCurrentlyChanging = false;
         mShown = false;
         mOldMainBounds.setEmpty();
         mOldSideBounds.setEmpty();
-        mResizingBounds.setEmpty();
+        mInstantaneousBounds.setEmpty();
     }
 
     /** Showing resizing hint. */
     public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
             Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
             boolean immediately) {
-        if (mResizingIconView == null) {
+        if (mVeilIconView == null) {
             return;
         }
 
-        if (!mIsResizing) {
-            mIsResizing = true;
+        if (!mIsCurrentlyChanging) {
+            mIsCurrentlyChanging = true;
             mOldMainBounds.set(newBounds);
             mOldSideBounds.set(sideBounds);
         }
-        mResizingBounds.set(newBounds);
+        mInstantaneousBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
@@ -254,8 +258,8 @@
 
         if (mIcon == null && resizingTask.topActivityInfo != null) {
             mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
-            mResizingIconView.setImageDrawable(mIcon);
-            mResizingIconView.setVisibility(View.VISIBLE);
+            mVeilIconView.setImageDrawable(mIcon);
+            mVeilIconView.setVisibility(View.VISIBLE);
 
             WindowManager.LayoutParams lp =
                     (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
@@ -275,7 +279,12 @@
                 t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
                 t.setVisibility(mIconLeash, showVeil);
             } else {
-                startFadeAnimation(showVeil, false, null);
+                startFadeAnimation(
+                        showVeil,
+                        false /* releaseSurface */,
+                        null /* finishedCallback */,
+                        false /* addDelay */
+                );
             }
             mShown = showVeil;
         }
@@ -320,19 +329,19 @@
             mScreenshotAnimator.start();
         }
 
-        if (mResizingIconView == null) {
+        if (mVeilIconView == null) {
             if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                 animFinishedCallback.accept(false);
             }
             return;
         }
 
-        mIsResizing = false;
+        mIsCurrentlyChanging = false;
         mOffsetX = 0;
         mOffsetY = 0;
         mOldMainBounds.setEmpty();
         mOldSideBounds.setEmpty();
-        mResizingBounds.setEmpty();
+        mInstantaneousBounds.setEmpty();
         if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
             if (!mShown) {
                 // If fade-out animation is running, just add release callback to it.
@@ -356,7 +365,7 @@
                 if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                     animFinishedCallback.accept(true);
                 }
-            });
+            }, false /* addDelay */);
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
@@ -366,9 +375,94 @@
         }
     }
 
+    /**
+     * Called (on every frame) when two split apps are swapping, and a veil is needed.
+     */
+    public void drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask,
+            Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind,
+            SurfaceControl leash, float iconOffsetX, float iconOffsetY) {
+        if (mVeilIconView == null) {
+            return;
+        }
+
+        if (!mIsCurrentlyChanging) {
+            mIsCurrentlyChanging = true;
+        }
+
+        mInstantaneousBounds.set(newBounds);
+        mOffsetX = (int) iconOffsetX;
+        mOffsetY = (int) iconOffsetY;
+
+        t.setLayer(leash, isGoingBehind ? BEHIND_APP_VEIL_LAYER : FRONT_APP_VEIL_LAYER);
+
+        if (!mShown) {
+            if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+                // Cancel mFadeAnimator if it is running
+                mFadeAnimator.cancel();
+            }
+        }
+
+        if (mBackgroundLeash == null) {
+            // Initialize background
+            mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+                    RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+                    .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
+        }
+
+        if (mIcon == null && resizingTask.topActivityInfo != null) {
+            // Initialize icon
+            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+            mVeilIconView.setImageDrawable(mIcon);
+            mVeilIconView.setVisibility(View.VISIBLE);
+
+            WindowManager.LayoutParams lp =
+                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            mViewHost.relayout(lp);
+
+            t.setLayer(mIconLeash, Integer.MAX_VALUE);
+        }
+
+        t.setPosition(mIconLeash,
+                newBounds.width() / 2 - mIconSize / 2 - mOffsetX,
+                newBounds.height() / 2 - mIconSize / 2 - mOffsetY);
+
+        // If this is the first frame, we need to trigger the veil's fade-in animation.
+        if (!mShown) {
+            startFadeAnimation(
+                    true /* show */,
+                    false /* releaseSurface */,
+                    null /* finishedCallball */,
+                    false /* addDelay */
+            );
+            mShown = true;
+        }
+    }
+
+    /** Called at the end of the swap animation. */
+    public void fadeOutVeilAndCleanUp(SurfaceControl.Transaction t) {
+        if (mVeilIconView == null) {
+            return;
+        }
+
+        // Recenter icon
+        t.setPosition(mIconLeash,
+                mInstantaneousBounds.width() / 2f - mIconSize / 2f,
+                mInstantaneousBounds.height() / 2f - mIconSize / 2f);
+
+        mIsCurrentlyChanging = false;
+        mOffsetX = 0;
+        mOffsetY = 0;
+        mInstantaneousBounds.setEmpty();
+
+        fadeOutDecor(() -> {}, true /* addDelay */);
+    }
+
     /** Screenshot host leash and attach on it if meet some conditions */
     public void screenshotIfNeeded(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
@@ -386,7 +480,7 @@
     public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
         if (screenshot == null || !screenshot.isValid()) return;
 
-        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
@@ -401,24 +495,35 @@
 
     /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback
      * directly. */
-    public void fadeOutDecor(Runnable finishedCallback) {
+    public void fadeOutDecor(Runnable finishedCallback, boolean addDelay) {
         if (mShown) {
             // If previous animation is running, just cancel it.
             if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
                 mFadeAnimator.cancel();
             }
 
-            startFadeAnimation(false /* show */, true, finishedCallback);
+            startFadeAnimation(
+                    false /* show */, true /* releaseSurface */, finishedCallback, addDelay);
             mShown = false;
         } else {
             if (finishedCallback != null) finishedCallback.run();
         }
     }
 
+    /**
+     * Fades the veil in or out. Called at the first frame of a movement or resize when a veil is
+     * needed (with show = true), and called again at the end (with show = false).
+     * @param addDelay If true, adds a short delay before fading out to get the app behind the veil
+     *                 time to redraw.
+     */
     private void startFadeAnimation(boolean show, boolean releaseSurface,
-            Runnable finishedCallback) {
+            Runnable finishedCallback, boolean addDelay) {
         final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+
         mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
+        if (addDelay) {
+            mFadeAnimator.setStartDelay(VEIL_DELAY_DURATION);
+        }
         mFadeAnimator.setDuration(FADE_DURATION);
         mFadeAnimator.addUpdateListener(valueAnimator-> {
             final float progress = (float) valueAnimator.getAnimatedValue();
@@ -481,8 +586,8 @@
         }
 
         if (mIcon != null) {
-            mResizingIconView.setVisibility(View.GONE);
-            mResizingIconView.setImageDrawable(null);
+            mVeilIconView.setVisibility(View.GONE);
+            mVeilIconView.setImageDrawable(null);
             t.hide(mIconLeash);
             mIcon = null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 51f9de8..0e050694 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -53,6 +53,8 @@
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -68,10 +70,12 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageTaskListener;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -87,10 +91,29 @@
     public static final int PARALLAX_ALIGN_CENTER = 2;
 
     public static final int FLING_RESIZE_DURATION = 250;
-    private static final int FLING_SWITCH_DURATION = 350;
     private static final int FLING_ENTER_DURATION = 450;
     private static final int FLING_EXIT_DURATION = 450;
 
+    // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
+    // layers stay in order. Note: This does not affect any other layer numbering systems because
+    // the layer system in WindowManager is local within sibling groups. So, for example, each
+    // "veil layer" defined here actually has two sub-layers; and *their* layer values, which we set
+    // in SplitDecorManager, are only important relative to each other.
+    public static final int DIVIDER_LAYER = 0;
+    public static final int FRONT_APP_VEIL_LAYER = DIVIDER_LAYER + 20;
+    public static final int FRONT_APP_LAYER = DIVIDER_LAYER + 10;
+    public static final int BEHIND_APP_VEIL_LAYER = DIVIDER_LAYER - 10;
+    public static final int BEHIND_APP_LAYER = DIVIDER_LAYER - 20;
+
+    // Animation specs for the swap animation
+    private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
+    private static final float SWAP_ANIMATION_SHRINK_DURATION = 83;
+    private static final float SWAP_ANIMATION_SHRINK_MARGIN_DP = 14;
+    private static final Interpolator SHRINK_INTERPOLATOR =
+            new PathInterpolator(0.2f, 0f, 0f, 1f);
+    private static final Interpolator GROW_INTERPOLATOR =
+            new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+
     private int mDividerWindowWidth;
     private int mDividerInsets;
     private int mDividerSize;
@@ -134,6 +157,7 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mIsLeftRightSplit;
     private ValueAnimator mDividerFlingAnimator;
+    private AnimatorSet mSwapAnimator;
 
     public SplitLayout(String windowName, Context context, Configuration configuration,
             SplitLayoutHandler splitLayoutHandler,
@@ -579,6 +603,10 @@
     }
 
     void onDoubleTappedDivider() {
+        if (isCurrentlySwapping()) {
+            return;
+        }
+
         mSplitLayoutHandler.onDoubleTappedDivider();
     }
 
@@ -685,36 +713,43 @@
     }
 
     /** Switch both surface position with animation. */
-    public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
-            SurfaceControl leash2, Consumer<Rect> finishCallback) {
+    public void playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage,
+            StageTaskListener bottomRightStage, Consumer<Rect> finishCallback) {
         final Rect insets = getDisplayStableInsets(mContext);
+        // If we have insets in the direction of the swap, the animation won't look correct because
+        // window contents will shift and redraw again at the end. So we show a veil to hide that.
         insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
                 mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
+        final boolean shouldVeil =
+                insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;
 
         final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
                 mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
-        final Rect distBounds1 = new Rect();
-        final Rect distBounds2 = new Rect();
-        final Rect distDividerBounds = new Rect();
-        // Compute dist bounds.
-        updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
+        final Rect endBounds1 = new Rect();
+        final Rect endBounds2 = new Rect();
+        final Rect endDividerBounds = new Rect();
+        // Compute destination bounds.
+        updateBounds(dividerPos, endBounds2, endBounds1, endDividerBounds,
                 false /* setEffectBounds */);
         // Offset to real position under root container.
-        distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
-        distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
-        distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
+        endBounds1.offset(-mRootBounds.left, -mRootBounds.top);
+        endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
+        endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
 
-        ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
-                -insets.left, -insets.top);
-        ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
-                insets.left, insets.top);
-        ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
-                distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
+        ValueAnimator animator1 = moveSurface(t, topLeftStage, getRefBounds1(), endBounds1,
+                -insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
+                shouldVeil);
+        ValueAnimator animator2 = moveSurface(t, bottomRightStage, getRefBounds2(), endBounds2,
+                insets.left, insets.top, true /* roundCorners */, false /* isGoingBehind */,
+                shouldVeil);
+        ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
+                endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
+                false /* isGoingBehind */, false /* addVeil */);
 
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animator1, animator2, animator3);
-        set.setDuration(FLING_SWITCH_DURATION);
-        set.addListener(new AnimatorListenerAdapter() {
+        mSwapAnimator = new AnimatorSet();
+        mSwapAnimator.playTogether(animator1, animator2, animator3);
+        mSwapAnimator.setDuration(SWAP_ANIMATION_TOTAL_DURATION);
+        mSwapAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mInteractionJankMonitor.begin(getDividerLeash(),
@@ -734,36 +769,144 @@
                 mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
             }
         });
-        set.start();
+        mSwapAnimator.start();
     }
 
-    private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
-            Rect start, Rect end, float offsetX, float offsetY) {
+    /** Returns true if a swap animation is currently playing. */
+    public boolean isCurrentlySwapping() {
+        return mSwapAnimator != null && mSwapAnimator.isRunning();
+    }
+
+    /**
+     * Animates a task leash across the screen. Currently used only for the swap animation.
+     *
+     * @param stage The stage holding the task being animated. If null, it is the divider.
+     * @param roundCorners Whether we should round the corners of the task while animating.
+     * @param isGoingBehind Whether we should a shrink-and-grow effect to the task while it is
+     *                           moving. (Simulates moving behind the divider.)
+     */
+    private ValueAnimator moveSurface(SurfaceControl.Transaction t, StageTaskListener stage,
+            Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners,
+            boolean isGoingBehind, boolean addVeil) {
+        final boolean isApp = stage != null; // check if this is an app or a divider
+        final SurfaceControl leash = isApp ? stage.getRootLeash() : getDividerLeash();
+        final ActivityManager.RunningTaskInfo taskInfo = isApp ? stage.getRunningTaskInfo() : null;
+        final SplitDecorManager decorManager = isApp ? stage.getDecorManager() : null;
+
         Rect tempStart = new Rect(start);
         Rect tempEnd = new Rect(end);
         final float diffX = tempEnd.left - tempStart.left;
         final float diffY = tempEnd.top - tempStart.top;
         final float diffWidth = tempEnd.width() - tempStart.width();
         final float diffHeight = tempEnd.height() - tempStart.height();
+
+        // Get display measurements (for possible shrink animation).
+        final RoundedCorner roundedCorner = mSplitWindowManager.getDividerView().getDisplay()
+                .getRoundedCorner(0 /* position */);
+        float cornerRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+        float shrinkMarginPx = PipUtils.dpToPx(
+                SWAP_ANIMATION_SHRINK_MARGIN_DP, mContext.getResources().getDisplayMetrics());
+        float shrinkAmountPx = shrinkMarginPx * 2;
+
+        // Timing calculations
+        float shrinkPortion = SWAP_ANIMATION_SHRINK_DURATION / SWAP_ANIMATION_TOTAL_DURATION;
+        float growPortion = 1 - shrinkPortion;
+
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+        animator.setInterpolator(Interpolators.EMPHASIZED);
         animator.addUpdateListener(animation -> {
             if (leash == null) return;
+            if (roundCorners) {
+                // Add rounded corners to the task leash while it is animating.
+                t.setCornerRadius(leash, cornerRadius);
+            }
 
-            final float scale = (float) animation.getAnimatedValue();
-            final float distX = tempStart.left + scale * diffX;
-            final float distY = tempStart.top + scale * diffY;
-            final int width = (int) (tempStart.width() + scale * diffWidth);
-            final int height = (int) (tempStart.height() + scale * diffHeight);
-            if (offsetX == 0 && offsetY == 0) {
-                t.setPosition(leash, distX, distY);
-                t.setWindowCrop(leash, width, height);
+            final float progress = (float) animation.getAnimatedValue();
+            float instantaneousX = tempStart.left + progress * diffX;
+            float instantaneousY = tempStart.top + progress * diffY;
+            int width = (int) (tempStart.width() + progress * diffWidth);
+            int height = (int) (tempStart.height() + progress * diffHeight);
+
+            if (isGoingBehind) {
+                float shrinkDiffX; // the position adjustments needed for this frame
+                float shrinkDiffY;
+                float shrinkScaleX; // the scale adjustments needed for this frame
+                float shrinkScaleY;
+
+                // Find the max amount we will be shrinking this leash, as a proportion (e.g. 0.1f).
+                float maxShrinkX = shrinkAmountPx / height;
+                float maxShrinkY = shrinkAmountPx / width;
+
+                // Find if we are in the shrinking part of the animation, or the growing part.
+                boolean shrinking = progress <= shrinkPortion;
+
+                if (shrinking) {
+                    // Find how far into the shrink portion we are (e.g. 0.5f).
+                    float shrinkProgress = progress / shrinkPortion;
+                    // Find how much we should have progressed in shrinking the leash (e.g. 0.8f).
+                    float interpolatedShrinkProgress =
+                            SHRINK_INTERPOLATOR.getInterpolation(shrinkProgress);
+                    // Find how much width proportion we should be taking off (e.g. 0.1f)
+                    float widthProportionLost =  maxShrinkX * interpolatedShrinkProgress;
+                    shrinkScaleX = 1 - widthProportionLost;
+                    // Find how much height proportion we should be taking off (e.g. 0.1f)
+                    float heightProportionLost =  maxShrinkY * interpolatedShrinkProgress;
+                    shrinkScaleY = 1 - heightProportionLost;
+                    // Add a small amount to the leash's position to keep the task centered.
+                    shrinkDiffX = (width * widthProportionLost) / 2;
+                    shrinkDiffY = (height * heightProportionLost) / 2;
+                } else {
+                    // Find how far into the grow portion we are (e.g. 0.5f).
+                    float growProgress = (progress - shrinkPortion) / growPortion;
+                    // Find how much we should have progressed in growing the leash (e.g. 0.8f).
+                    float interpolatedGrowProgress =
+                            GROW_INTERPOLATOR.getInterpolation(growProgress);
+                    // Find how much width proportion we should be taking off (e.g. 0.1f)
+                    float widthProportionLost =  maxShrinkX * (1 - interpolatedGrowProgress);
+                    shrinkScaleX = 1 - widthProportionLost;
+                    // Find how much height proportion we should be taking off (e.g. 0.1f)
+                    float heightProportionLost =  maxShrinkY * (1 - interpolatedGrowProgress);
+                    shrinkScaleY = 1 - heightProportionLost;
+                    // Add a small amount to the leash's position to keep the task centered.
+                    shrinkDiffX = (width * widthProportionLost) / 2;
+                    shrinkDiffY = (height * heightProportionLost) / 2;
+                }
+
+                instantaneousX += shrinkDiffX;
+                instantaneousY += shrinkDiffY;
+                width *= shrinkScaleX;
+                height *= shrinkScaleY;
+                // Set scale on the leash's contents.
+                t.setScale(leash, shrinkScaleX, shrinkScaleY);
+            }
+
+            // Set layers
+            if (taskInfo != null) {
+                t.setLayer(leash, isGoingBehind ? BEHIND_APP_LAYER : FRONT_APP_LAYER);
             } else {
-                final int diffOffsetX = (int) (scale * offsetX);
-                final int diffOffsetY = (int) (scale * offsetY);
-                t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
+                t.setLayer(leash, DIVIDER_LAYER);
+            }
+
+            if (offsetX == 0 && offsetY == 0) {
+                t.setPosition(leash, instantaneousX, instantaneousY);
+                mTempRect.set((int) instantaneousX, (int) instantaneousY,
+                        (int) (instantaneousX + width), (int) (instantaneousY + height));
+                t.setWindowCrop(leash, width, height);
+                if (addVeil) {
+                    decorManager.drawNextVeilFrameForSwapAnimation(
+                            taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
+                }
+            } else {
+                final int diffOffsetX = (int) (progress * offsetX);
+                final int diffOffsetY = (int) (progress * offsetY);
+                t.setPosition(leash, instantaneousX + diffOffsetX, instantaneousY + diffOffsetY);
                 mTempRect.set(0, 0, width, height);
                 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
                 t.setCrop(leash, mTempRect);
+                if (addVeil) {
+                    decorManager.drawNextVeilFrameForSwapAnimation(
+                            taskInfo, mTempRect, t, isGoingBehind, leash, diffOffsetX, diffOffsetY);
+                }
             }
             t.apply();
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index e8c809e..8c06de7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -29,6 +29,8 @@
 public class SplitScreenConstants {
     /** Duration used for every split fade-in or fade-out. */
     public static final int FADE_DURATION = 133;
+    /** Duration where we keep an app veiled to allow it to redraw itself behind the scenes. */
+    public static final int VEIL_DELAY_DURATION = 400;
 
     /** Key for passing in widget intents when invoking split from launcher workspace. */
     public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 7c0455e..c2ee223 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -186,6 +186,9 @@
      */
     private boolean mIsFirstReachabilityEducationRunning;
 
+    @NonNull
+    private final CompatUIStatusManager mCompatUIStatusManager;
+
     public CompatUIController(@NonNull Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -198,7 +201,8 @@
             @NonNull DockStateReader dockStateReader,
             @NonNull CompatUIConfiguration compatUIConfiguration,
             @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
-            @NonNull AccessibilityManager accessibilityManager) {
+            @NonNull AccessibilityManager accessibilityManager,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -213,6 +217,7 @@
         mCompatUIShellCommandHandler = compatUIShellCommandHandler;
         mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
                 DISAPPEAR_DELAY_MS, flags);
+        mCompatUIStatusManager = compatUIStatusManager;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -520,7 +525,7 @@
                 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
                 mTransitionsLazy.get(),
                 stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
-                mDockStateReader, mCompatUIConfiguration);
+                mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
     }
 
     private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
new file mode 100644
index 0000000..915a8a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.annotation.NonNull;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/** Handle the visibility state of the Compat UI components. */
+public class CompatUIStatusManager {
+
+    public static final int COMPAT_UI_EDUCATION_HIDDEN = 0;
+    public static final int COMPAT_UI_EDUCATION_VISIBLE = 1;
+
+    @NonNull
+    private final IntConsumer mWriter;
+    @NonNull
+    private final IntSupplier mReader;
+
+    public CompatUIStatusManager(@NonNull IntConsumer writer, @NonNull IntSupplier reader) {
+        mWriter = writer;
+        mReader = reader;
+    }
+
+    public CompatUIStatusManager() {
+        this(i -> { }, () -> COMPAT_UI_EDUCATION_HIDDEN);
+    }
+
+    void onEducationShown() {
+        mWriter.accept(COMPAT_UI_EDUCATION_VISIBLE);
+    }
+
+    void onEducationHidden() {
+        mWriter.accept(COMPAT_UI_EDUCATION_HIDDEN);
+    }
+
+    boolean isEducationVisible() {
+        return mReader.getAsInt() == COMPAT_UI_EDUCATION_VISIBLE;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index 2347032..3124a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -76,15 +77,19 @@
 
     private final DockStateReader mDockStateReader;
 
+    @NonNull
+    private final CompatUIStatusManager mCompatUIStatusManager;
+
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions,
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
-            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
                 onDismissCallback,
                 new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
-                dockStateReader, compatUIConfiguration);
+                dockStateReader, compatUIConfiguration, compatUIStatusManager);
     }
 
     @VisibleForTesting
@@ -93,7 +98,8 @@
             DisplayLayout displayLayout, Transitions transitions,
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
             DialogAnimationController<LetterboxEduDialogLayout> animationController,
-            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+            @NonNull CompatUIStatusManager compatUIStatusManager) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -103,6 +109,7 @@
                 R.dimen.letterbox_education_dialog_margin);
         mDockStateReader = dockStateReader;
         mCompatUIConfiguration = compatUIConfiguration;
+        mCompatUIStatusManager = compatUIStatusManager;
         mEligibleForLetterboxEducation =
                 taskInfo.appCompatTaskInfo.eligibleForLetterboxEducation();
     }
@@ -139,7 +146,7 @@
     protected View createLayout() {
         mLayout = inflateLayout();
         updateDialogMargins();
-
+        mCompatUIStatusManager.onEducationShown();
         // startEnterAnimation will be called immediately if shell-transitions are disabled.
         mTransitions.runOnIdle(this::startEnterAnimation);
         return mLayout;
@@ -199,6 +206,7 @@
     @Override
     public void release() {
         mAnimationController.cancelAnimation();
+        mCompatUIStatusManager.onEducationHidden();
         super.release();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f22dcce..04cd225 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.dagger;
 
+import static android.provider.Settings.Secure.COMPAT_UI_EDUCATION_SHOWING;
+
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
 import static com.android.wm.shell.onehanded.OneHandedController.SUPPORT_ONE_HANDED_MODE;
 
 import android.annotation.NonNull;
@@ -24,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.window.SystemPerformanceHinter;
@@ -72,6 +76,7 @@
 import com.android.wm.shell.compatui.CompatUIConfiguration;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.CompatUIStatusManager;
 import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator;
 import com.android.wm.shell.compatui.api.CompatUIHandler;
 import com.android.wm.shell.compatui.api.CompatUIRepository;
@@ -254,7 +259,8 @@
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
             @NonNull CompatUIState compatUIState,
-            @NonNull CompatUIComponentIdGenerator componentIdGenerator) {
+            @NonNull CompatUIComponentIdGenerator componentIdGenerator,
+            CompatUIStatusManager compatUIStatusManager) {
         if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
             return Optional.empty();
         }
@@ -276,7 +282,22 @@
                         dockStateReader.get(),
                         compatUIConfiguration.get(),
                         compatUIShellCommandHandler.get(),
-                        accessibilityManager.get()));
+                        accessibilityManager.get(),
+                        compatUIStatusManager));
+    }
+
+    @WMSingleton
+    @Provides
+    static CompatUIStatusManager provideCompatUIStatusManager(@NonNull Context context) {
+        if (Flags.enableCompatUiVisibilityStatus()) {
+            return new CompatUIStatusManager(
+                    newState -> Settings.Secure.putInt(context.getContentResolver(),
+                            COMPAT_UI_EDUCATION_SHOWING, newState),
+                    () -> Settings.Secure.getInt(context.getContentResolver(),
+                            COMPAT_UI_EDUCATION_SHOWING, COMPAT_UI_EDUCATION_HIDDEN));
+        } else {
+            return new CompatUIStatusManager();
+        }
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a18bbad..b8b62a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -58,6 +59,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,6 +70,7 @@
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
+import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.GlobalDragListener;
@@ -603,10 +606,12 @@
             Context context,
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter,
             InteractionJankMonitor interactionJankMonitor) {
-        return new DragToDesktopTransitionHandler(context, transitions,
-                rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+        return Flags.enableDesktopWindowingTransitions()
+                ? new SpringDragToDesktopTransitionHandler(context, transitions,
+                        rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+                : new DefaultDragToDesktopTransitionHandler(context, transitions,
+                        rootTaskDisplayAreaOrganizer, interactionJankMonitor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 3e7b4fe..6c03dc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -54,6 +54,12 @@
     // Instead default to the desired initial bounds.
     val stableBounds = Rect()
     displayLayout.getStableBoundsForDesktopMode(stableBounds)
+    if (hasFullscreenOverride(taskInfo)) {
+        // If the activity has a fullscreen override applied, it should be treated as
+        // resizeable and match the device orientation. Thus the ideal size can be
+        // applied.
+        return positionInScreen(idealSize, stableBounds)
+    }
     val topActivityInfo =
         taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds)
 
@@ -62,13 +68,17 @@
             ORIENTATION_LANDSCAPE -> {
                 if (taskInfo.isResizeable) {
                     if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
-                        // Respect apps fullscreen width
+                        // For portrait resizeable activities, respect apps fullscreen width but
+                        // apply ideal size height.
                         Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
                             idealSize.height)
                     } else {
+                        // For landscape resizeable activities, simply apply ideal size.
                         idealSize
                     }
                 } else {
+                    // If activity is unresizeable, regardless of orientation, calculate maximum
+                    // size (within the ideal size) maintaining original aspect ratio.
                     maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                 }
             }
@@ -77,23 +87,29 @@
                     screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
                 if (taskInfo.isResizeable) {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
-                        // Respect apps fullscreen height and apply custom app width
+                        // For landscape resizeable activities, respect apps fullscreen height and
+                        // apply custom app width.
                         Size(
                             customPortraitWidthForLandscapeApp,
                             taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
                         )
                     } else {
+                        // For portrait resizeable activities, simply apply ideal size.
                         idealSize
                     }
                 } else {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
-                        // Apply custom app width and calculate maximum size
+                        // For landscape unresizeable activities, apply custom app width to ideal
+                        // size and calculate maximum size with this area while maintaining original
+                        // aspect ratio.
                         maximizeSizeGivenAspectRatio(
                             taskInfo,
                             Size(customPortraitWidthForLandscapeApp, idealSize.height),
                             appAspectRatio
                         )
                     } else {
+                        // For portrait unresizeable activities, calculate maximum size (within the
+                        // ideal size) maintaining original aspect ratio.
                         maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
                     }
                 }
@@ -209,3 +225,8 @@
         else -> isFixedOrientationPortrait(configuration.orientation)
     }
 }
+
+private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean {
+    return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled
+            || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 6011db7..09f9139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -27,6 +27,7 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
 import android.content.Context;
@@ -262,12 +263,22 @@
 
     /**
      * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
+     *
+     * @param finishCallback called when animation ends or gets cancelled
      */
-    private void fadeOutIndicator() {
+    void fadeOutIndicator(@Nullable Runnable finishCallback) {
         final VisualIndicatorAnimator animator = VisualIndicatorAnimator
                 .fadeBoundsOut(mView, mCurrentType,
                         mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
+        if (finishCallback != null) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    finishCallback.run();
+                }
+            });
+        }
         mCurrentType = IndicatorType.NO_INDICATOR;
     }
 
@@ -282,7 +293,7 @@
         if (mCurrentType == IndicatorType.NO_INDICATOR) {
             fadeInIndicator(newType);
         } else if (newType == IndicatorType.NO_INDICATOR) {
-            fadeOutIndicator();
+            fadeOutIndicator(null /* finishCallback */);
         } else {
             final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
                     mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e154da5..78d41b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -163,8 +163,10 @@
             }
 
             private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
-                visualIndicator?.releaseVisualIndicator(tx)
-                visualIndicator = null
+                visualIndicator?.fadeOutIndicator {
+                    visualIndicator?.releaseVisualIndicator(tx)
+                    visualIndicator = null
+                }
             }
         }
 
@@ -193,7 +195,7 @@
         )
         transitions.addHandler(this)
         taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
-        dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
+        dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener
         recentsTransitionHandler.addTransitionStateListener(
             object : RecentsTransitionStateListener {
                 override fun onAnimationStateChanged(running: Boolean) {
@@ -213,7 +215,7 @@
     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
         toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
         enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
-        dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+        dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
     }
 
     fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 5221a45..9874f4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -27,19 +27,21 @@
 import android.window.TransitionInfo
 import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
+import androidx.dynamicanimation.animation.SpringForce
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.FloatProperties
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -50,40 +52,31 @@
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.util.function.Supplier
+import kotlin.math.max
 
 /**
  * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
  * handles the cancellation case where the task is dragged back to the status bar area in the same
  * gesture.
+ *
+ * It's a base sealed class that delegates flag dependant logic to its subclasses:
+ * [DefaultDragToDesktopTransitionHandler] and [SpringDragToDesktopTransitionHandler]
+ *
+ * TODO(b/356764679): Clean up after the full flag rollout
  */
-class DragToDesktopTransitionHandler(
+sealed class DragToDesktopTransitionHandler(
     private val context: Context,
     private val transitions: Transitions,
     private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    private val interactionJankMonitor: InteractionJankMonitor,
-    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+    protected val interactionJankMonitor: InteractionJankMonitor,
+    protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
 ) : TransitionHandler {
 
-    constructor(
-        context: Context,
-        transitions: Transitions,
-        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-        interactionJankMonitor: InteractionJankMonitor
-    ) : this(
-        context,
-        transitions,
-        rootTaskDisplayAreaOrganizer,
-        interactionJankMonitor,
-        Supplier { SurfaceControl.Transaction() }
-    )
-
-    private val rectEvaluator = RectEvaluator(Rect())
+    protected val rectEvaluator = RectEvaluator(Rect())
     private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
 
-    private var dragToDesktopStateListener: DragToDesktopStateListener? = null
     private lateinit var splitScreenController: SplitScreenController
     private var transitionState: TransitionState? = null
-    private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     /** Whether a drag-to-desktop transition is in progress. */
     val inProgress: Boolean
@@ -92,20 +85,18 @@
     /** The task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId: Int
         get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
-    /** Sets a listener to receive callback about events during the transition animation. */
-    fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
-        dragToDesktopStateListener = listener
-    }
+
+    /** Listener to receive callback about events during the transition animation. */
+    var dragToDesktopStateListener: DragToDesktopStateListener? = null
+
+    /** Task listener for animation start, task bounds resize, and the animation finish */
+    lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     /** Setter needed to avoid cyclic dependency. */
     fun setSplitScreenController(controller: SplitScreenController) {
         splitScreenController = controller
     }
 
-    fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
-        onTaskResizeAnimationListener = listener
-    }
-
     /**
      * Starts a transition that performs a transient launch of Home so that Home is brought to the
      * front while still keeping the currently focused task that is being dragged resumed. This
@@ -307,24 +298,18 @@
             return false
         }
 
-        // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom,
-        // then Home on top of that, wallpaper on top of that and finally the dragged task on top
-        // of everything.
-        val appLayers = info.changes.size
-        val homeLayers = info.changes.size * 2
-        val wallpaperLayers = info.changes.size * 3
-        val dragLayer = wallpaperLayers
+        val layers = calculateStartDragToDesktopLayers(info)
         val leafTaskFilter = TransitionUtil.LeafTaskFilter()
         info.changes.withIndex().forEach { (i, change) ->
             if (TransitionUtil.isWallpaper(change)) {
-                val layer = wallpaperLayers - i
+                val layer = layers.wallpaperLayers - i
                 startTransaction.apply {
                     setLayer(change.leash, layer)
                     show(change.leash)
                 }
             } else if (isHomeChange(change)) {
-                state.homeToken = change.container
-                val layer = homeLayers - i
+                state.homeChange = change
+                val layer = layers.homeLayers - i
                 startTransaction.apply {
                     setLayer(change.leash, layer)
                     show(change.leash)
@@ -338,11 +323,11 @@
                             if (state.cancelState == CancelState.NO_CANCEL) {
                                 // Normal case, split root goes to the bottom behind everything
                                 // else.
-                                appLayers - i
+                                layers.appLayers - i
                             } else {
                                 // Cancel-early case, pretend nothing happened so split root stays
                                 // top.
-                                dragLayer
+                                layers.dragLayer
                             }
                         startTransaction.apply {
                             setLayer(change.leash, layer)
@@ -357,7 +342,7 @@
                             state.draggedTaskChange = change
                             val bounds = change.endAbsBounds
                             startTransaction.apply {
-                                setLayer(change.leash, dragLayer)
+                                setLayer(change.leash, layers.dragLayer)
                                 setWindowCrop(change.leash, bounds.width(), bounds.height())
                                 show(change.leash)
                             }
@@ -370,7 +355,7 @@
                             state.otherRootChanges.add(change)
                             val bounds = change.endAbsBounds
                             startTransaction.apply {
-                                setLayer(change.leash, appLayers - i)
+                                setLayer(change.leash, layers.appLayers - i)
                                 setWindowCrop(change.leash, bounds.width(), bounds.height())
                                 show(change.leash)
                             }
@@ -404,7 +389,7 @@
                     )
                     val bounds = change.endAbsBounds
                     startTransaction.apply {
-                        setLayer(change.leash, dragLayer)
+                        setLayer(change.leash, layers.dragLayer)
                         setWindowCrop(change.leash, bounds.width(), bounds.height())
                         show(change.leash)
                     }
@@ -452,6 +437,15 @@
         return true
     }
 
+    /**
+     * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
+     * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
+     * the change index.
+     */
+    protected abstract fun calculateStartDragToDesktopLayers(
+        info: TransitionInfo
+    ): DragToDesktopLayers
+
     override fun mergeAnimation(
         transition: IBinder,
         info: TransitionInfo,
@@ -483,104 +477,14 @@
             state.startTransitionFinishCb
                 ?: error("Start transition expected to be waiting for merge but wasn't")
         if (isEndTransition) {
-            info.changes.withIndex().forEach { (i, change) ->
-                // If we're exiting split, hide the remaining split task.
-                if (
-                    state is TransitionState.FromSplit &&
-                        change.taskInfo?.taskId == state.otherSplitTask
-                ) {
-                    t.hide(change.leash)
-                    startTransactionFinishT.hide(change.leash)
-                }
-                if (change.mode == TRANSIT_CLOSE) {
-                    t.hide(change.leash)
-                    startTransactionFinishT.hide(change.leash)
-                } else if (change.taskInfo?.taskId == state.draggedTaskId) {
-                    t.show(change.leash)
-                    startTransactionFinishT.show(change.leash)
-                    state.draggedTaskChange = change
-                } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
-                    // Other freeform tasks that are being restored go behind the dragged task.
-                    val draggedTaskLeash =
-                        state.draggedTaskChange?.leash
-                            ?: error("Expected dragged leash to be non-null")
-                    t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
-                    startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
-                }
-            }
-
-            val draggedTaskChange =
-                state.draggedTaskChange
-                    ?: throw IllegalStateException("Expected non-null change of dragged task")
-            val draggedTaskLeash = draggedTaskChange.leash
-            val startBounds = draggedTaskChange.startAbsBounds
-            val endBounds = draggedTaskChange.endAbsBounds
-
-            // Pause any animation that may be currently playing; we will use the relevant
-            // details of that animation here.
-            state.dragAnimator.cancelAnimator()
-            // We still apply scale to task bounds; as we animate the bounds to their
-            // end value, animate scale to 1.
-            val startScale = state.dragAnimator.scale
-            val startPosition = state.dragAnimator.position
-            val unscaledStartWidth = startBounds.width()
-            val unscaledStartHeight = startBounds.height()
-            val unscaledStartBounds =
-                Rect(
-                    startPosition.x.toInt(),
-                    startPosition.y.toInt(),
-                    startPosition.x.toInt() + unscaledStartWidth,
-                    startPosition.y.toInt() + unscaledStartHeight
-                )
-
-            dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
-            // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
-            // and finish callback. Show the veil and position the task at the first frame before
-            // starting the final animation.
-            onTaskResizeAnimationListener.onAnimationStart(
-                state.draggedTaskId,
-                t,
-                unscaledStartBounds
+            setupEndDragToDesktop(
+                info,
+                startTransaction = t,
+                finishTransaction = startTransactionFinishT
             )
+            // Call finishCallback to merge animation before startTransitionFinishCb is called
             finishCallback.onTransitionFinished(null /* wct */)
-            val tx: SurfaceControl.Transaction = transactionSupplier.get()
-            ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
-                .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
-                .apply {
-                    addUpdateListener { animator ->
-                        val animBounds = animator.animatedValue as Rect
-                        val animFraction = animator.animatedFraction
-                        // Progress scale from starting value to 1 as animation plays.
-                        val animScale = startScale + animFraction * (1 - startScale)
-                        tx.apply {
-                            setScale(draggedTaskLeash, animScale, animScale)
-                            setPosition(
-                                draggedTaskLeash,
-                                animBounds.left.toFloat(),
-                                animBounds.top.toFloat()
-                            )
-                            setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
-                        }
-                        onTaskResizeAnimationListener.onBoundsChange(
-                            state.draggedTaskId,
-                            tx,
-                            animBounds
-                        )
-                    }
-                    addListener(
-                        object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator) {
-                                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
-                                startTransitionFinishCb.onTransitionFinished(null /* null */)
-                                clearState()
-                                interactionJankMonitor.end(
-                                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
-                                )
-                            }
-                        }
-                    )
-                    start()
-                }
+            animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
         } else if (isCancelTransition) {
             info.changes.forEach { change ->
                 t.show(change.leash)
@@ -593,6 +497,122 @@
         }
     }
 
+    protected open fun setupEndDragToDesktop(
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        val state = requireTransitionState()
+        val freeformTaskChanges = mutableListOf<Change>()
+        info.changes.forEachIndexed { i, change ->
+            when {
+                state is TransitionState.FromSplit &&
+                    change.taskInfo?.taskId == state.otherSplitTask -> {
+                    // If we're exiting split, hide the remaining split task.
+                    startTransaction.hide(change.leash)
+                    finishTransaction.hide(change.leash)
+                }
+                change.mode == TRANSIT_CLOSE -> {
+                    startTransaction.hide(change.leash)
+                    finishTransaction.hide(change.leash)
+                }
+                change.taskInfo?.taskId == state.draggedTaskId -> {
+                    startTransaction.show(change.leash)
+                    finishTransaction.show(change.leash)
+                    state.draggedTaskChange = change
+                }
+                change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM -> {
+                    // Other freeform tasks that are being restored go behind the dragged task.
+                    val draggedTaskLeash =
+                        state.draggedTaskChange?.leash
+                            ?: error("Expected dragged leash to be non-null")
+                    startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+                    finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+                    freeformTaskChanges.add(change)
+                }
+            }
+        }
+
+        state.freeformTaskChanges = freeformTaskChanges
+    }
+
+    protected open fun animateEndDragToDesktop(
+        startTransaction: SurfaceControl.Transaction,
+        startTransitionFinishCb: Transitions.TransitionFinishCallback
+    ) {
+        val state = requireTransitionState()
+        val draggedTaskChange =
+            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+        val draggedTaskLeash = draggedTaskChange.leash
+        val startBounds = draggedTaskChange.startAbsBounds
+        val endBounds = draggedTaskChange.endAbsBounds
+
+        // Cancel any animation that may be currently playing; we will use the relevant
+        // details of that animation here.
+        state.dragAnimator.cancelAnimator()
+        // We still apply scale to task bounds; as we animate the bounds to their
+        // end value, animate scale to 1.
+        val startScale = state.dragAnimator.scale
+        val startPosition = state.dragAnimator.position
+        val unscaledStartWidth = startBounds.width()
+        val unscaledStartHeight = startBounds.height()
+        val unscaledStartBounds =
+            Rect(
+                startPosition.x.toInt(),
+                startPosition.y.toInt(),
+                startPosition.x.toInt() + unscaledStartWidth,
+                startPosition.y.toInt() + unscaledStartHeight
+            )
+
+        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+        // and finish callback. Show the veil and position the task at the first frame before
+        // starting the final animation.
+        onTaskResizeAnimationListener.onAnimationStart(
+            state.draggedTaskId,
+            startTransaction,
+            unscaledStartBounds
+        )
+        val tx: SurfaceControl.Transaction = transactionSupplier.get()
+        ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
+            .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+            .apply {
+                addUpdateListener { animator ->
+                    val animBounds = animator.animatedValue as Rect
+                    val animFraction = animator.animatedFraction
+                    // Progress scale from starting value to 1 as animation plays.
+                    val animScale = startScale + animFraction * (1 - startScale)
+                    tx.apply {
+                        setScale(draggedTaskLeash, animScale, animScale)
+                        setPosition(
+                            draggedTaskLeash,
+                            animBounds.left.toFloat(),
+                            animBounds.top.toFloat()
+                        )
+                        setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
+                    }
+                    onTaskResizeAnimationListener.onBoundsChange(
+                        state.draggedTaskId,
+                        tx,
+                        animBounds
+                    )
+                }
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+                            startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                            clearState()
+                            interactionJankMonitor.end(
+                                CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+                            )
+                        }
+                    }
+                )
+                start()
+            }
+    }
+
     override fun handleRequest(
         transition: IBinder,
         request: TransitionRequestInfo
@@ -707,11 +727,12 @@
                 wct.reorder(wc, true /* toTop */)
             }
         }
-        val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
+        val homeWc =
+            state.homeChange?.container ?: error("Home task should be non-null before cancelling")
         wct.restoreTransientOrder(homeWc)
     }
 
-    private fun clearState() {
+    protected fun clearState() {
         transitionState = null
     }
 
@@ -731,10 +752,21 @@
         return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
     }
 
-    private fun requireTransitionState(): TransitionState {
+    protected fun requireTransitionState(): TransitionState {
         return transitionState ?: error("Expected non-null transition state")
     }
 
+    /**
+     * Represents the layering (Z order) that will be given to any window based on its type during
+     * the "start" transition of the drag-to-desktop transition
+     */
+    protected data class DragToDesktopLayers(
+        val appLayers: Int,
+        val homeLayers: Int,
+        val wallpaperLayers: Int,
+        val dragLayer: Int,
+    )
+
     interface DragToDesktopStateListener {
         fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
 
@@ -748,8 +780,9 @@
         abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
         abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
         abstract var cancelTransitionToken: IBinder?
-        abstract var homeToken: WindowContainerToken?
+        abstract var homeChange: Change?
         abstract var draggedTaskChange: Change?
+        abstract var freeformTaskChanges: List<Change>
         abstract var cancelState: CancelState
         abstract var startAborted: Boolean
 
@@ -760,8 +793,9 @@
             override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
             override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
             override var cancelTransitionToken: IBinder? = null,
-            override var homeToken: WindowContainerToken? = null,
+            override var homeChange: Change? = null,
             override var draggedTaskChange: Change? = null,
+            override var freeformTaskChanges: List<Change> = emptyList(),
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             var otherRootChanges: MutableList<Change> = mutableListOf()
@@ -774,8 +808,9 @@
             override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
             override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
             override var cancelTransitionToken: IBinder? = null,
-            override var homeToken: WindowContainerToken? = null,
+            override var homeChange: Change? = null,
             override var draggedTaskChange: Change? = null,
+            override var freeformTaskChanges: List<Change> = emptyList(),
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             var splitRootChange: Change? = null,
@@ -797,6 +832,210 @@
 
     companion object {
         /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
-        private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+        internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+    }
+}
+
+/** Enables flagged rollout of the [SpringDragToDesktopTransitionHandler] */
+class DefaultDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+    context: Context,
+    transitions: Transitions,
+    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    interactionJankMonitor: InteractionJankMonitor,
+    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+        SurfaceControl.Transaction()
+    },
+) :
+    DragToDesktopTransitionHandler(
+        context,
+        transitions,
+        taskDisplayAreaOrganizer,
+        interactionJankMonitor,
+        transactionSupplier
+    ) {
+
+    /**
+     * @return layers in order:
+     * - appLayers - non-wallpaper, non-home tasks excluding the dragged task go at the bottom
+     * - homeLayers - home task on top of apps
+     * - wallpaperLayers - wallpaper on top of home
+     * - dragLayer - the dragged task on top of everything, there's only 1 dragged task
+     */
+    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+        DragToDesktopLayers(
+            appLayers = info.changes.size,
+            homeLayers = info.changes.size * 2,
+            wallpaperLayers = info.changes.size * 3,
+            dragLayer = info.changes.size * 3
+        )
+}
+
+/** Desktop transition handler with spring based animation for the end drag to desktop transition */
+class SpringDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+    context: Context,
+    transitions: Transitions,
+    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    interactionJankMonitor: InteractionJankMonitor,
+    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+        SurfaceControl.Transaction()
+    },
+) :
+    DragToDesktopTransitionHandler(
+        context,
+        transitions,
+        taskDisplayAreaOrganizer,
+        interactionJankMonitor,
+        transactionSupplier
+    ) {
+
+    private val positionSpringConfig =
+        PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_LOW,
+            SpringForce.DAMPING_RATIO_LOW_BOUNCY
+        )
+
+    private val sizeSpringConfig =
+        PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+
+    /**
+     * @return layers in order:
+     * - appLayers - below everything z < 0, effectively hides the leash
+     * - homeLayers - home task on top of apps, z in 0..<size
+     * - wallpaperLayers - wallpaper on top of home, z in size..<size*2
+     * - dragLayer - the dragged task on top of everything, z == size*2
+     */
+    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+        DragToDesktopLayers(
+            appLayers = -1,
+            homeLayers = info.changes.size - 1,
+            wallpaperLayers = info.changes.size * 2 - 1,
+            dragLayer = info.changes.size * 2
+        )
+
+    override fun setupEndDragToDesktop(
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction
+    ) {
+        super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
+
+        val state = requireTransitionState()
+        val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
+        // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
+        finishTransaction.hide(homeLeash)
+        // Setup freeform tasks before animation
+        state.freeformTaskChanges.forEach { change ->
+            val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+            val startX =
+                change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
+            val startY =
+                change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2
+            startTransaction.setPosition(change.leash, startX, startY)
+            startTransaction.setScale(change.leash, startScale, startScale)
+            startTransaction.setAlpha(change.leash, 0f)
+        }
+    }
+
+    override fun animateEndDragToDesktop(
+        startTransaction: SurfaceControl.Transaction,
+        startTransitionFinishCb: Transitions.TransitionFinishCallback
+    ) {
+        val state = requireTransitionState()
+        val draggedTaskChange =
+            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+        val draggedTaskLeash = draggedTaskChange.leash
+        val freeformTaskChanges = state.freeformTaskChanges
+        val startBounds = draggedTaskChange.startAbsBounds
+        val endBounds = draggedTaskChange.endAbsBounds
+        val currentVelocity = state.dragAnimator.computeCurrentVelocity()
+
+        // Cancel any animation that may be currently playing; we will use the relevant
+        // details of that animation here.
+        state.dragAnimator.cancelAnimator()
+        // We still apply scale to task bounds; as we animate the bounds to their
+        // end value, animate scale to 1.
+        val startScale = state.dragAnimator.scale
+        val startPosition = state.dragAnimator.position
+        val startBoundsWithOffset =
+            Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
+
+        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+        // and finish callback. Show the veil and position the task at the first frame before
+        // starting the final animation.
+        onTaskResizeAnimationListener.onAnimationStart(
+            state.draggedTaskId,
+            startTransaction,
+            startBoundsWithOffset
+        )
+
+        val tx: SurfaceControl.Transaction = transactionSupplier.get()
+        PhysicsAnimator.getInstance(startBoundsWithOffset)
+            .spring(
+                FloatProperties.RECT_X,
+                endBounds.left.toFloat(),
+                currentVelocity.x,
+                positionSpringConfig
+            )
+            .spring(
+                FloatProperties.RECT_Y,
+                endBounds.top.toFloat(),
+                currentVelocity.y,
+                positionSpringConfig
+            )
+            .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
+            .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
+            .addUpdateListener { animBounds, _ ->
+                val animFraction =
+                    (animBounds.width() - startBounds.width()).toFloat() /
+                        (endBounds.width() - startBounds.width())
+                val animScale = startScale + animFraction * (1 - startScale)
+                // Freeform animation starts 50% in the animation
+                val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
+                val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+                val freeformAnimScale =
+                    freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
+                tx.apply {
+                    // Update dragged task
+                    setScale(draggedTaskLeash, animScale, animScale)
+                    setPosition(
+                        draggedTaskLeash,
+                        animBounds.left.toFloat(),
+                        animBounds.top.toFloat()
+                    )
+                    // Update freeform tasks
+                    freeformTaskChanges.forEach {
+                        val startX =
+                            it.endAbsBounds.left +
+                                it.endAbsBounds.width() * (1 - freeformAnimScale) / 2
+                        val startY =
+                            it.endAbsBounds.top +
+                                it.endAbsBounds.height() * (1 - freeformAnimScale) / 2
+                        setPosition(it.leash, startX, startY)
+                        setScale(it.leash, freeformAnimScale, freeformAnimScale)
+                        setAlpha(it.leash, freeformAnimFraction)
+                    }
+                }
+                onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds)
+            }
+            .withEndActions({
+                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+                startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                clearState()
+                interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+            })
+            .start()
+    }
+
+    companion object {
+        /**
+         * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
+         * gesture.
+         */
+        private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 284620e..da6221e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -632,6 +632,12 @@
                     public void insetsChanged(InsetsState insetsState) {
                         DisplayLayout pendingLayout = mDisplayController
                                 .getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
+                        if (pendingLayout == null) {
+                            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                    "insetsChanged: no display layout for displayId=%d",
+                                    mPipDisplayLayoutState.getDisplayId());
+                            return;
+                        }
                         if (mIsInFixedRotation
                                 || mIsKeyguardShowingOrAnimating
                                 || pendingLayout.rotation()
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 a7551bd..87dc16a 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
@@ -123,10 +123,10 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -1010,40 +1010,41 @@
         mTempRect1.setEmpty();
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
-        final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
-                topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-        final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
-                bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
-        mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
+
+        // Don't allow windows or divider to be focused during animation (mRootTaskInfo is the
+        // parent of all 3 leaves). We don't want the user to be able to tap and focus a window
+        // while it is moving across the screen, because granting focus also recalculates the
+        // layering order, which is in delicate balance during this animation.
+        WindowContainerTransaction noFocus = new WindowContainerTransaction();
+        noFocus.setFocusable(mRootTaskInfo.token, false);
+        mSyncQueue.queue(noFocus);
+
+        mSplitLayout.playSwapAnimation(t, topLeftStage, bottomRightStage,
                 insets -> {
+                    // Runs at the end of the swap animation
+                    SplitDecorManager decorManager1 = topLeftStage.getDecorManager();
+                    SplitDecorManager decorManager2 = bottomRightStage.getDecorManager();
+
                     WindowContainerTransaction wct = new WindowContainerTransaction();
+
+                    // Restore focus-ability to the windows and divider
+                    wct.setFocusable(mRootTaskInfo.token, true);
+
                     setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
                     mSyncQueue.queue(wct);
                     mSyncQueue.runInSync(st -> {
                         updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
-                        st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
-                        st.setPosition(bottomRightScreenshot, insets.left, insets.top);
 
-                        final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
-                        va.addUpdateListener(valueAnimator-> {
-                            final float progress = (float) valueAnimator.getAnimatedValue();
-                            t.setAlpha(topLeftScreenshot, progress);
-                            t.setAlpha(bottomRightScreenshot, progress);
-                            t.apply();
-                        });
-                        va.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(
-                                    @androidx.annotation.NonNull Animator animation) {
-                                t.remove(topLeftScreenshot);
-                                t.remove(bottomRightScreenshot);
-                                t.apply();
-                                mTransactionPool.release(t);
-                            }
-                        });
-                        va.start();
+                        // updateSurfaceBounds(), above, officially puts the two apps in their new
+                        // stages. Starting on the next frame, all calculations are made using the
+                        // new layouts/insets. So any follow-up animations on the same leashes below
+                        // should contain some cleanup/repositioning to prevent jank.
+
+                        // Play follow-up animations if needed
+                        decorManager1.fadeOutVeilAndCleanUp(st);
+                        decorManager2.fadeOutVeilAndCleanUp(st);
                     });
                 });
 
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 d1ab3e9..f19eb3f 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
@@ -69,7 +69,7 @@
  *
  * @see StageCoordinator
  */
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = StageTaskListener.class.getSimpleName();
 
     /** Callback interface for listening to changes in a split-screen stage. */
@@ -162,6 +162,18 @@
         return getChildTaskInfo(predicate) != null;
     }
 
+    public SurfaceControl getRootLeash() {
+        return mRootLeash;
+    }
+
+    public ActivityManager.RunningTaskInfo getRunningTaskInfo() {
+        return mRootTaskInfo;
+    }
+
+    public SplitDecorManager getDecorManager() {
+        return mSplitDecorManager;
+    }
+
     @Nullable
     private ActivityManager.RunningTaskInfo getChildTaskInfo(
             Predicate<ActivityManager.RunningTaskInfo> predicate) {
@@ -335,7 +347,7 @@
 
     void fadeOutDecor(Runnable finishedCallback) {
         if (mSplitDecorManager != null) {
-            mSplitDecorManager.fadeOutDecor(finishedCallback);
+            mSplitDecorManager.fadeOutDecor(finishedCallback, false /* addDelay */);
         } else {
             finishedCallback.run();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index f372557..1a38449 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.graphics.Color.WHITE;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import android.app.ActivityManager;
@@ -69,8 +68,9 @@
             // Can't show splash screen on requested display, so skip showing at all.
             return;
         }
+        final int theme = getSplashScreenTheme(0 /* splashScreenThemeResId */, activityInfo);
         final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
-                0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+                theme, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
         if (myContext == null) {
             return;
         }
@@ -86,19 +86,11 @@
         final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
         lp.width = windowBounds.width();
         lp.height = windowBounds.height();
-        final ActivityManager.TaskDescription taskDescription;
-        if (taskInfo.taskDescription != null) {
-            taskDescription = taskInfo.taskDescription;
-        } else {
-            taskDescription = new ActivityManager.TaskDescription();
-            taskDescription.setBackgroundColor(WHITE);
-        }
 
         final FrameLayout rootLayout = new FrameLayout(
                 mSplashscreenContentDrawer.createViewContextWrapper(myContext));
         viewHost.setView(rootLayout, lp);
-
-        final int bgColor = taskDescription.getBackgroundColor();
+        final int bgColor = mSplashscreenContentDrawer.estimateTaskBackgroundColor(myContext);
         final SplashScreenView splashScreenView = mSplashscreenContentDrawer
                 .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
         rootLayout.addView(splashScreenView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 75e7ddf..a27c14bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -19,7 +19,9 @@
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_NONE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -221,6 +223,15 @@
      */
     public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
         final int type = info.getType();
+        // This back navigation is canceled, check whether the transition should be open or close
+        if (type == TRANSIT_PREPARE_BACK_NAVIGATION
+                || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+            if (!info.getChanges().isEmpty()) {
+                final TransitionInfo.Change change = info.getChanges().get(0);
+                return TransitionUtil.isOpeningMode(change.getMode())
+                        ? TRANSIT_OPEN : TRANSIT_CLOSE;
+            }
+        }
         // If the info transition type is opening transition, iterate its changes to see if it
         // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
         if (type == TRANSIT_OPEN) {
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 a242b8a..8c8f205 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
@@ -69,7 +69,6 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
-import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -115,6 +114,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
 
@@ -321,7 +321,7 @@
     private void onInit() {
         mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
         mShellCommandHandler.addDumpCallback(this::dump, this);
-        mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+        mDisplayInsetsController.addGlobalInsetsChangedListener(
                 new DesktopModeOnInsetsChangedListener());
         mDesktopTasksController.setOnTaskResizeAnimationListener(
                 new DesktopModeOnTaskResizeAnimationListener());
@@ -1196,10 +1196,6 @@
                 && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
             return false;
         }
-        if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
-                && taskInfo.isFocused) {
-            return false;
-        }
         if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
                 && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
             return false;
@@ -1397,19 +1393,17 @@
         }
     }
 
-    static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
-        private boolean mIsKeyguardVisible;
-        private boolean mIsKeyguardOccluded;
-
+    class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
         @Override
         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
                 boolean animatingDismiss) {
-            mIsKeyguardVisible = visible;
-            mIsKeyguardOccluded = occluded;
-        }
-
-        public boolean isKeyguardVisibleAndOccluded() {
-            return mIsKeyguardVisible && mIsKeyguardOccluded;
+            final int size = mWindowDecorByTaskId.size();
+            for (int i = size - 1; i >= 0; i--) {
+                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+                if (decor != null) {
+                    decor.onKeyguardStateChanged(visible, occluded);
+                }
+            }
         }
     }
 
@@ -1417,28 +1411,26 @@
     class DesktopModeOnInsetsChangedListener implements
             DisplayInsetsController.OnInsetsChangedListener {
         @Override
-        public void insetsChanged(InsetsState insetsState) {
-            for (int i = 0; i < insetsState.sourceSize(); i++) {
-                final InsetsSource source = insetsState.sourceAt(i);
-                if (source.getType() != statusBars()) {
+        public void insetsChanged(int displayId, @NonNull InsetsState insetsState) {
+            final int size = mWindowDecorByTaskId.size();
+            for (int i = size - 1; i >= 0; i--) {
+                final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+                if (decor == null) {
                     continue;
                 }
-
-                final DesktopModeWindowDecoration decor = getFocusedDecor();
-                if (decor == null) {
-                    return;
+                if (decor.mTaskInfo.displayId == displayId
+                        && Flags.enableDesktopWindowingImmersiveHandleHiding()) {
+                    decor.onInsetsStateChanged(insetsState);
                 }
-                // If status bar inset is visible, top task is not in immersive mode
-                final boolean inImmersiveMode = !source.isVisible();
-                // Calls WindowDecoration#relayout if decoration visibility needs to be updated
-                if (inImmersiveMode != mInImmersiveMode) {
-                    if (Flags.enableDesktopWindowingImmersiveHandleHiding()) {
-                        decor.relayout(decor.mTaskInfo);
-                    }
-                    mInImmersiveMode = inImmersiveMode;
+                if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+                    // If status bar inset is visible, top task is not in immersive mode.
+                    // This value is only needed when the App Handle input is being handled
+                    // through the global input monitor (hence the flag check) to ignore gestures
+                    // when the app is in immersive mode. When disabled, the view itself handles
+                    // input, and since it's removed when in immersive there's no need to track
+                    // this here.
+                    mInImmersiveMode = !InsetsStateKt.isVisible(insetsState, statusBars());
                 }
-
-                return;
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 54b33e93..095d337 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -60,6 +60,7 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
@@ -120,8 +121,9 @@
 
     /** Closes the maximize window and releases its view. */
     fun close() {
-        maximizeMenuView?.cancelAnimation()
-        maximizeMenu?.releaseView()
+        maximizeMenuView?.animateCloseMenu {
+            maximizeMenu?.releaseView()
+        }
         maximizeMenu = null
         maximizeMenuView = null
     }
@@ -255,7 +257,7 @@
             .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
 
         private val hoverTempRect = Rect()
-        private val openMenuAnimatorSet = AnimatorSet()
+        private var menuAnimatorSet: AnimatorSet? = null
         private lateinit var taskInfo: RunningTaskInfo
         private lateinit var style: MenuStyle
 
@@ -346,15 +348,16 @@
         fun animateOpenMenu() {
             maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
-            openMenuAnimatorSet.playTogether(
+            menuAnimatorSet = AnimatorSet()
+            menuAnimatorSet?.playTogether(
                 ObjectAnimator.ofFloat(rootView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
                     .apply {
-                        duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                        duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                         interpolator = EMPHASIZED_DECELERATE
                     },
                 ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f)
                     .apply {
-                        duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                        duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                         interpolator = EMPHASIZED_DECELERATE
                         addUpdateListener {
                             // Animate padding so that controls stay pinned to the bottom of
@@ -367,7 +370,7 @@
                         }
                     },
                 ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply {
-                    duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                    duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                     interpolator = EMPHASIZED_DECELERATE
                     addUpdateListener {
                         // Scale up the children of the maximize menu so that the menu
@@ -381,7 +384,7 @@
                 },
                 ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
                     (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
-                    duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+                    duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
                     interpolator = EMPHASIZED_DECELERATE
                 },
                 ObjectAnimator.ofInt(rootView.background, "alpha",
@@ -391,7 +394,7 @@
                 ValueAnimator.ofFloat(0f, 1f)
                     .apply {
                         duration = ALPHA_ANIMATION_DURATION_MS
-                        startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+                        startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
                         addUpdateListener {
                             val value = animatedValue as Float
                             maximizeButton.alpha = value
@@ -403,21 +406,96 @@
                 ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION)
                     .apply {
                         duration = ELEVATION_ANIMATION_DURATION_MS
-                        startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+                        startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
                     }
             )
-            openMenuAnimatorSet.addListener(
+            menuAnimatorSet?.addListener(
                 onEnd = {
                     maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
                     maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
                 }
             )
-            openMenuAnimatorSet.start()
+            menuAnimatorSet?.start()
         }
 
-        /** Cancel the open menu animation. */
-        fun cancelAnimation() {
-            openMenuAnimatorSet.cancel()
+        /** Animate the closing of the menu */
+        fun animateCloseMenu(onEnd: (() -> Unit)) {
+            maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            cancelAnimation()
+            menuAnimatorSet = AnimatorSet()
+            menuAnimatorSet?.playTogether(
+                    ObjectAnimator.ofFloat(rootView, SCALE_Y, 1f, STARTING_MENU_HEIGHT_SCALE)
+                            .apply {
+                                duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                                interpolator = FAST_OUT_LINEAR_IN
+                            },
+                    ValueAnimator.ofFloat(1f, STARTING_MENU_HEIGHT_SCALE)
+                            .apply {
+                                duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                                interpolator = FAST_OUT_LINEAR_IN
+                                addUpdateListener {
+                                    // Animate padding so that controls stay pinned to the bottom of
+                                    // the menu.
+                                    val value = animatedValue as Float
+                                    val topPadding = menuPadding -
+                                            ((1 - value) * menuHeight).toInt()
+                                    container.setPadding(menuPadding, topPadding,
+                                            menuPadding, menuPadding)
+                                }
+                            },
+                    ValueAnimator.ofFloat(1f, 1 / STARTING_MENU_HEIGHT_SCALE).apply {
+                        duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                        interpolator = FAST_OUT_LINEAR_IN
+                        addUpdateListener {
+                            // Scale up the children of the maximize menu so that the menu
+                            // scale is cancelled out and only the background is scaled.
+                            val value = animatedValue as Float
+                            maximizeButton.scaleY = value
+                            snapButtonsLayout.scaleY = value
+                            maximizeText.scaleY = value
+                            snapWindowText.scaleY = value
+                        }
+                    },
+                    ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
+                            0f, (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight).apply {
+                        duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+                        interpolator = FAST_OUT_LINEAR_IN
+                    },
+                    ObjectAnimator.ofInt(rootView.background, "alpha",
+                            MAX_DRAWABLE_ALPHA_VALUE, 0).apply {
+                        startDelay = CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS
+                        duration = ALPHA_ANIMATION_DURATION_MS
+                    },
+                    ValueAnimator.ofFloat(1f, 0f)
+                            .apply {
+                                duration = ALPHA_ANIMATION_DURATION_MS
+                                addUpdateListener {
+                                    val value = animatedValue as Float
+                                    maximizeButton.alpha = value
+                                    snapButtonsLayout.alpha = value
+                                    maximizeText.alpha = value
+                                    snapWindowText.alpha = value
+                                }
+                            },
+                    ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION, 0f)
+                            .apply {
+                                duration = ELEVATION_ANIMATION_DURATION_MS
+                            }
+            )
+            menuAnimatorSet?.addListener(
+                    onEnd = {
+                        maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+                        maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+                        onEnd?.invoke()
+                    }
+            )
+            menuAnimatorSet?.start()
+        }
+
+        /** Cancel the menu animation. */
+        private fun cancelAnimation() {
+            menuAnimatorSet?.cancel()
         }
 
         /** Update the view state to a new snap to half selection. */
@@ -645,9 +723,11 @@
         private const val ALPHA_ANIMATION_DURATION_MS = 50L
         private const val MAX_DRAWABLE_ALPHA_VALUE = 255
         private const val STARTING_MENU_HEIGHT_SCALE = 0.8f
-        private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+        private const val OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+        private const val CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS = 200L
         private const val ELEVATION_ANIMATION_DURATION_MS = 50L
-        private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
+        private const val CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS = 33L
+        private const val CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS = 33L
         private const val MENU_Z_TRANSLATION = 1f
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 9741667..70c0b54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -7,6 +7,7 @@
 import android.graphics.Rect
 import android.view.MotionEvent
 import android.view.SurfaceControl
+import android.view.VelocityTracker
 import com.android.wm.shell.R
 
 /**
@@ -34,6 +35,7 @@
     val scale: Float
         get() = dragToDesktopAnimator.animatedValue as Float
     private val mostRecentInput = PointF()
+    private val velocityTracker = VelocityTracker.obtain()
     private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
             DRAG_FREEFORM_SCALE)
             .setDuration(ANIMATION_DURATION.toLong())
@@ -90,6 +92,7 @@
         if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) {
             return
         }
+        velocityTracker.addMovement(ev)
         setTaskPosition(ev.rawX, ev.rawY)
         val t = transactionFactory()
         t.setPosition(taskSurface, position.x, position.y)
@@ -109,6 +112,15 @@
      * Cancels the animation, intended to be used when another animator will take over.
      */
     fun cancelAnimator() {
+        velocityTracker.clear()
         dragToDesktopAnimator.cancel()
     }
+
+    /**
+     * Computes the current velocity per second based on the points that have been collected.
+     */
+    fun computeCurrentVelocity(): PointF {
+        velocityTracker.computeCurrentVelocity(/* units = */ 1000)
+        return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity)
+    }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0c58987..4af5b2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -61,6 +61,7 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -143,6 +144,9 @@
     TaskDragResizer mTaskDragResizer;
     boolean mIsCaptionVisible;
 
+    private boolean mIsStatusBarVisible;
+    private boolean mIsKeyguardVisibleAndOccluded;
+
     /** The most recent set of insets applied to this window decoration. */
     private WindowDecorationInsets mWindowDecorationInsets;
     private final Binder mOwner = new Binder();
@@ -184,6 +188,9 @@
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+        final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
+        mIsStatusBarVisible = insetsState != null
+                && InsetsStateKt.isVisible(insetsState, statusBars());
     }
 
     /**
@@ -234,7 +241,7 @@
         }
         rootView = null; // Clear it just in case we use it accidentally
 
-        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+        updateCaptionVisibility(outResult.mRootView);
 
         final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
         outResult.mWidth = taskBounds.width();
@@ -284,17 +291,20 @@
             mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
             mDecorWindowContext.setTheme(mContext.getThemeResId());
             if (params.mLayoutResId != 0) {
-                outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                        .inflate(params.mLayoutResId, null);
+                outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
             }
         }
 
         if (outResult.mRootView == null) {
-            outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                    .inflate(params.mLayoutResId, null);
+            outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
         }
     }
 
+    @VisibleForTesting
+    T inflateLayout(Context context, int layoutResId) {
+        return (T) LayoutInflater.from(context).inflate(layoutResId, null);
+    }
+
     private void updateDecorationContainerSurface(
             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
         if (mDecorationContainerSurface == null) {
@@ -497,22 +507,31 @@
         throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
     }
 
+    void onKeyguardStateChanged(boolean visible, boolean occluded) {
+        final boolean prevVisAndOccluded = mIsKeyguardVisibleAndOccluded;
+        mIsKeyguardVisibleAndOccluded = visible && occluded;
+        final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
+        if (changed) {
+            relayout(mTaskInfo);
+        }
+    }
+
+    void onInsetsStateChanged(@NonNull InsetsState insetsState) {
+        final boolean prevStatusBarVisibility = mIsStatusBarVisible;
+        mIsStatusBarVisible = InsetsStateKt.isVisible(insetsState, statusBars());
+        final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
+
+        if (changed) {
+            relayout(mTaskInfo);
+        }
+    }
+
     /**
      * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
      */
-    private void updateCaptionVisibility(View rootView, int displayId) {
-        final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
-        for (int i = 0; i < insetsState.sourceSize(); i++) {
-            final InsetsSource source = insetsState.sourceAt(i);
-            if (source.getType() != statusBars()) {
-                continue;
-            }
-
-            mIsCaptionVisible = source.isVisible();
-            setCaptionVisibility(rootView, mIsCaptionVisible);
-
-            return;
-        }
+    private void updateCaptionVisibility(View rootView) {
+        mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+        setCaptionVisibility(rootView, mIsCaptionVisible);
     }
 
     void setTaskDragResizer(TaskDragResizer taskDragResizer) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
new file mode 100644
index 0000000..be01a20
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.extension
+
+import android.view.InsetsState
+import android.view.WindowInsets
+
+/**
+ * Whether the source of the given [type] is visible or false if there is no source of that type.
+ */
+fun InsetsState.isVisible(@WindowInsets.Type.InsetsType type: Int): Boolean {
+    for (i in 0 until sourceSize()) {
+        val source = sourceAt(i)
+        if (source.type != type) {
+            continue
+        }
+        return source.isVisible
+    }
+    return false
+}
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
similarity index 63%
copy from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
index cd7a879..079ee13 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2024 The Android Open Source Project
   ~
@@ -13,10 +14,10 @@
   ~ 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.internal.overlay.hsumconfig"
-    android:versionCode="1"
-    android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
-</manifest>
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/caption"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="end"
+    android:background="@drawable/caption_decor_title"/>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 669e433..9df9956 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -160,6 +161,19 @@
         assertTrue(secondListener.hideInsetsCount == 1);
     }
 
+    @Test
+    public void testGlobalListenerCallback() throws RemoteException {
+        TrackedListener globalListener = new TrackedListener();
+        addDisplay(SECOND_DISPLAY);
+        mController.addGlobalInsetsChangedListener(globalListener);
+
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
+        mExecutor.flushAll();
+
+        assertEquals(2, globalListener.insetsChangedCount);
+    }
+
     private void addDisplay(int displayId) throws RemoteException {
         mController.onDisplayAdded(displayId);
         verify(mWm, times(mInsetsControllersByDisplayId.size() + 1))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index de1659b..b39cf19 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -43,6 +43,7 @@
 import android.view.InsetsState;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.window.flags.Flags;
@@ -128,6 +129,9 @@
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
 
+    @NonNull
+    private CompatUIStatusManager mCompatUIStatusManager;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -147,11 +151,13 @@
         doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
 
+        mCompatUIStatusManager = new CompatUIStatusManager();
         mShellInit = spy(new ShellInit(mMockExecutor));
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
-                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
+                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
+                mCompatUIStatusManager) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
new file mode 100644
index 0000000..d6059a8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/**
+ * Tests for {@link CompatUILayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:CompatUIStatusManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUIStatusManagerTest extends ShellTestCase {
+
+    private FakeCompatUIStatusManagerTest mTestState;
+    private CompatUIStatusManager mStatusManager;
+
+    @Before
+    public void setUp() {
+        mTestState = new FakeCompatUIStatusManagerTest();
+        mStatusManager = new CompatUIStatusManager(mTestState.mWriter, mTestState.mReader);
+    }
+
+    @Test
+    public void isEducationShown() {
+        assertFalse(mStatusManager.isEducationVisible());
+
+        mStatusManager.onEducationShown();
+        assertTrue(mStatusManager.isEducationVisible());
+
+        mStatusManager.onEducationHidden();
+        assertFalse(mStatusManager.isEducationVisible());
+    }
+
+    static class FakeCompatUIStatusManagerTest {
+
+        int mCurrentStatus = 0;
+
+        final IntSupplier mReader = () -> mCurrentStatus;
+
+        final IntConsumer mWriter = newStatus -> mCurrentStatus = newStatus;
+
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 7617269..94dbd11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -20,9 +20,13 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -38,6 +42,7 @@
 import android.app.TaskInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -54,6 +59,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -61,6 +67,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIStatusManagerTest.FakeCompatUIStatusManagerTest;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
@@ -120,6 +127,8 @@
 
     private CompatUIConfiguration mCompatUIConfiguration;
     private TestShellExecutor mExecutor;
+    private FakeCompatUIStatusManagerTest mCompatUIStatus;
+    private CompatUIStatusManager mCompatUIStatusManager;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -129,6 +138,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mExecutor = new TestShellExecutor();
+        mCompatUIStatus = new FakeCompatUIStatusManagerTest();
+        mCompatUIStatusManager = new CompatUIStatusManager(mCompatUIStatus.mWriter,
+                mCompatUIStatus.mReader);
         mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
 
             final Set<Integer> mHasSeenSet = new HashSet<>();
@@ -414,6 +426,21 @@
         assertFalse(windowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS)
+    public void testCompatUIStatus_dialogIsShown() {
+        // We display the dialog
+        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+                USER_ID_1, /* isTaskbarEduShowing= */ false);
+        assertTrue(windowManager.createLayout(/* canShow= */ true));
+        assertNotNull(windowManager.mLayout);
+        assertEquals(/* expected= */ COMPAT_UI_EDUCATION_VISIBLE, mCompatUIStatus.mCurrentStatus);
+
+        // We dismiss
+        windowManager.release();
+        assertEquals(/* expected= */ COMPAT_UI_EDUCATION_HIDDEN, mCompatUIStatus.mCurrentStatus);
+    }
+
     private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
             int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
             int expectedExtraBottomMargin) {
@@ -464,7 +491,7 @@
                 windowManager = new LetterboxEduWindowManager(mContext,
                 createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
                 createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
-                mDockStateReader, mCompatUIConfiguration);
+                mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
         spyOn(windowManager);
         doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
         doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 92f7050..3b85859 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -751,6 +751,50 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+  }
+
+  @Test
   fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
     val task = setUpFullscreenTask()
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2712,13 +2756,15 @@
   }
 
   private fun setUpFullscreenTask(
-      displayId: Int = DEFAULT_DISPLAY,
-      isResizable: Boolean = true,
-      windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
-      deviceOrientation: Int = ORIENTATION_LANDSCAPE,
-      screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
-      shouldLetterbox: Boolean = false,
-      gravity: Int = Gravity.NO_GRAVITY
+    displayId: Int = DEFAULT_DISPLAY,
+    isResizable: Boolean = true,
+    windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+    deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+    screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+    shouldLetterbox: Boolean = false,
+    gravity: Int = Gravity.NO_GRAVITY,
+    enableUserFullscreenOverride: Boolean = false,
+    enableSystemFullscreenOverride: Boolean = false
   ): RunningTaskInfo {
     val task = createFullscreenTask(displayId)
     val activityInfo = ActivityInfo()
@@ -2729,6 +2775,8 @@
       isResizeable = isResizable
       configuration.orientation = deviceOrientation
       configuration.windowConfiguration.windowingMode = windowingMode
+      appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+      appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
 
       if (shouldLetterbox) {
         if (deviceOrientation == ORIENTATION_LANDSCAPE &&
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index e4e2bd2..c97bcfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -11,6 +11,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
 import android.window.TransitionInfo
 import android.window.TransitionInfo.FLAG_IS_WALLPAPER
 import android.window.WindowContainerTransaction
@@ -27,6 +28,7 @@
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.util.function.Supplier
 import junit.framework.Assert.assertFalse
 import org.junit.Before
 import org.junit.Test
@@ -40,7 +42,6 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
-import java.util.function.Supplier
 
 /** Tests of [DragToDesktopTransitionHandler]. */
 @SmallTest
@@ -52,17 +53,26 @@
     @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock private lateinit var splitScreenController: SplitScreenController
     @Mock private lateinit var dragAnimator: MoveToDesktopAnimator
-    @Mock
-    private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var draggedTaskLeash: SurfaceControl
+    @Mock private lateinit var homeTaskLeash: SurfaceControl
 
     private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
 
-    private lateinit var handler: DragToDesktopTransitionHandler
+    private lateinit var defaultHandler: DragToDesktopTransitionHandler
+    private lateinit var springHandler: SpringDragToDesktopTransitionHandler
 
     @Before
     fun setUp() {
-        handler =
-            DragToDesktopTransitionHandler(
+        defaultHandler = DefaultDragToDesktopTransitionHandler(
+                    context,
+                    transitions,
+                    taskDisplayAreaOrganizer,
+                    mockInteractionJankMonitor,
+                    transactionSupplier,
+                )
+                .apply { setSplitScreenController(splitScreenController) }
+        springHandler = SpringDragToDesktopTransitionHandler(
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
@@ -76,10 +86,10 @@
     fun startDragToDesktop_animateDragWhenReady() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Now it's ready to animate.
-        handler.startAnimation(
+        defaultHandler.startAnimation(
             transition = transition,
             info =
                 createTransitionInfo(
@@ -96,65 +106,70 @@
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
+        )
         verify(transitions)
-            .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler))
+            .startTransition(
+                eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+                any(),
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT)
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_TOP_OR_LEFT),
-            any()
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
         )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
     }
 
     @Test
     fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() {
-        performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT)
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
-            any()
+        performEarlyCancel(
+            defaultHandler,
+            DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
         )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
     }
 
     @Test
     fun startDragToDesktop_aborted_finishDropped() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
         // But the transition was aborted.
-        handler.onTransitionConsumed(transition, aborted = true, mock())
+        defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
 
         // Attempt to finish the failed drag start.
-        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+        defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
 
         // Should not be attempted and state should be reset.
         verify(transitions, never())
-                .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
-        assertFalse(handler.inProgress)
+            .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+        assertFalse(defaultHandler.inProgress)
     }
 
     @Test
     fun startDragToDesktop_aborted_cancelDropped() {
         val task = createTask()
         // Simulate transition is started.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
         // But the transition was aborted.
-        handler.onTransitionConsumed(transition, aborted = true, mock())
+        defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
 
         // Attempt to finish the failed drag start.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
         // Should not be attempted and state should be reset.
-        assertFalse(handler.inProgress)
+        assertFalse(defaultHandler.inProgress)
     }
 
     @Test
@@ -162,23 +177,24 @@
         val task = createTask()
 
         // Simulate attempt to start two drag to desktop transitions.
-        startDragToDesktopTransition(task, dragAnimator)
-        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Verify transition only started once.
-        verify(transitions, times(1)).startTransition(
+        verify(transitions, times(1))
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun cancelDragToDesktop_startWasReady_cancel() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled after it had already started.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
@@ -188,48 +204,40 @@
 
     @Test
     fun cancelDragToDesktop_splitLeftCancelType_splitRequested() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled it, requesting split.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
         )
 
         // Verify the request went through split controller.
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_TOP_OR_LEFT),
-            any()
-        )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
     }
 
     @Test
     fun cancelDragToDesktop_splitRightCancelType_splitRequested() {
-        startDrag()
+        startDrag(defaultHandler)
 
         // Then user cancelled it, requesting split.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
         )
 
         // Verify the request went through split controller.
-        verify(splitScreenController).requestEnterSplitSelect(
-            any(),
-            any(),
-            eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
-            any()
-        )
+        verify(splitScreenController)
+            .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
     }
 
     @Test
     fun cancelDragToDesktop_startWasNotReady_animateCancel() {
         val task = createTask()
         // Simulate transition is started and is ready to animate.
-        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(defaultHandler, task, dragAnimator)
 
         // Then user cancelled before the transition was ready and animated.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
@@ -240,50 +248,139 @@
     @Test
     fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
         // Then cancel is called before the transition was started.
-        handler.cancelDragToDesktopTransition(
+        defaultHandler.cancelDragToDesktopTransition(
             DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
         )
 
         // Verify cancel is dropped.
-        verify(transitions, never()).startTransition(
+        verify(transitions, never())
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
     @Test
     fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
         // Then finish is called before the transition was started.
-        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+        defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
 
         // Verify finish is dropped.
-        verify(transitions, never()).startTransition(
+        verify(transitions, never())
+            .startTransition(
                 eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
                 any(),
-                eq(handler)
-        )
+                eq(defaultHandler)
+            )
     }
 
-    private fun startDrag() {
+    @Test
+    fun mergeAnimation_otherTransition_doesNotMerge() {
+        val transaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
+
+        startDrag(defaultHandler, task)
+        defaultHandler.mergeAnimation(
+            transition = mock(),
+            info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
+            t = transaction,
+            mergeTarget = mock(),
+            finishCallback = finishCallback
+        )
+
+        // Should NOT have any transaction changes
+        verifyZeroInteractions(transaction)
+        // Should NOT merge animation
+        verify(finishCallback, never()).onTransitionFinished(any())
+    }
+
+    @Test
+    fun mergeAnimation_endTransition_mergesAnimation() {
+        val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        val startTransition =
+            startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
+        defaultHandler.onTaskResizeAnimationListener = mock()
+
+        defaultHandler.mergeAnimation(
+            transition = mock(),
+            info =
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
+            t = mergedStartTransaction,
+            mergeTarget = startTransition,
+            finishCallback = finishCallback
+        )
+
+        // Should show dragged task layer in start and finish transaction
+        verify(mergedStartTransaction).show(draggedTaskLeash)
+        verify(playingFinishTransaction).show(draggedTaskLeash)
+        // Should merge animation
+        verify(finishCallback).onTransitionFinished(null)
+    }
+
+    @Test
+    fun mergeAnimation_endTransition_springHandler_hidesHome() {
+        whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+        val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        val startTransition =
+            startDrag(springHandler, task, finishTransaction = playingFinishTransaction)
+        springHandler.onTaskResizeAnimationListener = mock()
+
+        springHandler.mergeAnimation(
+            transition = mock(),
+            info =
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
+            t = mergedStartTransaction,
+            mergeTarget = startTransition,
+            finishCallback = finishCallback
+        )
+
+        // Should show dragged task layer in start and finish transaction
+        verify(mergedStartTransaction).show(draggedTaskLeash)
+        verify(playingFinishTransaction).show(draggedTaskLeash)
+        // Should hide home task leash in finish transaction
+        verify(playingFinishTransaction).hide(homeTaskLeash)
+        // Should merge animation
+        verify(finishCallback).onTransitionFinished(null)
+    }
+
+    private fun startDrag(
+        handler: DragToDesktopTransitionHandler,
+        task: RunningTaskInfo = createTask(),
+        finishTransaction: SurfaceControl.Transaction = mock()
+    ): IBinder {
         whenever(dragAnimator.position).thenReturn(PointF())
         // Simulate transition is started and is ready to animate.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(handler, task, dragAnimator)
         handler.startAnimation(
             transition = transition,
             info =
-            createTransitionInfo(
-                type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
-                draggedTask = task
-            ),
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
             startTransaction = mock(),
-            finishTransaction = mock(),
+            finishTransaction = finishTransaction,
             finishCallback = {}
         )
+        return transition
     }
 
     private fun startDragToDesktopTransition(
+        handler: DragToDesktopTransitionHandler,
         task: RunningTaskInfo,
         dragAnimator: MoveToDesktopAnimator
     ): IBinder {
@@ -300,20 +397,23 @@
         return token
     }
 
-    private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) {
+    private fun performEarlyCancel(
+        handler: DragToDesktopTransitionHandler,
+        cancelState: DragToDesktopTransitionHandler.CancelState
+    ) {
         val task = createTask()
         // Simulate transition is started and is ready to animate.
-        val transition = startDragToDesktopTransition(task, dragAnimator)
+        val transition = startDragToDesktopTransition(handler, task, dragAnimator)
 
         handler.cancelDragToDesktopTransition(cancelState)
 
         handler.startAnimation(
             transition = transition,
             info =
-            createTransitionInfo(
-                type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
-                draggedTask = task
-            ),
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+                    draggedTask = task
+                ),
             startTransaction = mock(),
             finishTransaction = mock(),
             finishCallback = {}
@@ -340,7 +440,7 @@
     private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo {
         return TransitionInfo(type, 0 /* flags */).apply {
             addChange( // Home.
-                TransitionInfo.Change(mock(), mock()).apply {
+                TransitionInfo.Change(mock(), homeTaskLeash).apply {
                     parent = null
                     taskInfo =
                         TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
@@ -348,7 +448,7 @@
                 }
             )
             addChange( // Dragged Task.
-                TransitionInfo.Change(mock(), mock()).apply {
+                TransitionInfo.Change(mock(), draggedTaskLeash).apply {
                     parent = null
                     taskInfo = draggedTask
                 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index ee9f886..af6c077 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -370,6 +370,6 @@
                 Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
                 false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
                 0 /* systemUiVisibility */, false /* isTranslucent */,
-                hasImeSurface /* hasImeSurface */);
+                hasImeSurface /* hasImeSurface */, 0 /* uiMode */);
     }
 }
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 68975ec..6d68797 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
@@ -36,7 +36,6 @@
 import android.os.Handler
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.platform.test.flag.junit.SetFlagsRule
@@ -56,7 +55,6 @@
 import android.view.SurfaceControl
 import android.view.SurfaceView
 import android.view.View
-import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
@@ -85,11 +83,11 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.splitscreen.SplitScreenController
-import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import java.util.Optional
 import java.util.function.Consumer
@@ -172,6 +170,7 @@
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
     private lateinit var displayChangingListener: DisplayChangeController.OnDisplayChangingListener
+    private lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
     private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
 
     @Before
@@ -225,17 +224,20 @@
 
         shellInit.init()
 
-        val insetListenerCaptor =
-            argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
-        verify(displayInsetsController)
-            .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture())
-        desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue
-
         val displayChangingListenerCaptor =
             argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
         verify(mockDisplayController)
             .addDisplayChangingController(displayChangingListenerCaptor.capture())
         displayChangingListener = displayChangingListenerCaptor.firstValue
+        val insetsChangedCaptor =
+                argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+        verify(displayInsetsController)
+            .addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
+        desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+        val keyguardChangedCaptor =
+            argumentCaptor<DesktopModeKeyguardChangeListener>()
+        verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
+        desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
     }
 
     @After
@@ -354,26 +356,6 @@
     }
 
     @Test
-    fun testCaptionIsNotCreatedWhenKeyguardIsVisible() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
-        val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>()
-        verify(mockShellController).addKeyguardChangeListener(keyguardListenerCaptor.capture())
-
-        keyguardListenerCaptor.firstValue.onKeyguardVisibilityChanged(
-                true /* visible */,
-                true /* occluded */,
-                false /* animatingDismiss */
-        )
-        onTaskOpening(task)
-
-        task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
-        task.setWindowingMode(ACTIVITY_TYPE_UNDEFINED)
-        onTaskChanging(task)
-
-        assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
-    }
-
-    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
@@ -418,67 +400,50 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
-
-        onTaskOpening(task)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
+    fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
+        val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
+        val decoration1 = setUpMockDecorationForTask(task1)
+        onTaskOpening(task1)
+        val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+        val decoration2 = setUpMockDecorationForTask(task2)
+        onTaskOpening(task2)
+        val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+        val decoration3 = setUpMockDecorationForTask(task3)
+        onTaskOpening(task3)
 
         // Add status bar insets source
-        val insetsState = InsetsState()
-        val statusBarInsetsSourceId = 0
-        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
-        statusBarInsetsSource.isVisible = false
-        insetsState.addSource(statusBarInsetsSource)
+        val insetsState = InsetsState().apply {
+            addSource(InsetsSource(0 /* id */, statusBars()).apply {
+                isVisible = false
+            })
+        }
+        desktopModeOnInsetsChangedListener.insetsChanged(2 /* displayId */, insetsState)
 
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout occurs when status bar inset visibility changes
-        verify(decoration, times(1)).relayout(task)
+        verify(decoration1, never()).onInsetsStateChanged(insetsState)
+        verify(decoration2).onInsetsStateChanged(insetsState)
+        verify(decoration3).onInsetsStateChanged(insetsState)
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
+    fun testKeyguardState_notifiesAllDecors() {
+        val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration1 = setUpMockDecorationForTask(task1)
+        onTaskOpening(task1)
+        val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration2 = setUpMockDecorationForTask(task2)
+        onTaskOpening(task2)
+        val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration3 = setUpMockDecorationForTask(task3)
+        onTaskOpening(task3)
 
-        onTaskOpening(task)
+        desktopModeOnKeyguardChangedListener
+            .onKeyguardVisibilityChanged(true /* visible */, true /* occluded */,
+                false /* animatingDismiss */)
 
-        // Add navigation bar insets source
-        val insetsState = InsetsState()
-        val navigationBarInsetsSourceId = 1
-        val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
-        navigationBarInsetsSource.isVisible = false
-        insetsState.addSource(navigationBarInsetsSource)
-
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout does not occur when non-status bar inset changes visibility
-        verify(decoration, never()).relayout(task)
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
-    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
-        val decoration = setUpMockDecorationForTask(task)
-
-        onTaskOpening(task)
-
-        // Add status bar insets source
-        val insetsState = InsetsState()
-        val statusBarInsetsSourceId = 0
-        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
-        statusBarInsetsSource.isVisible = false
-        insetsState.addSource(statusBarInsetsSource)
-
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
-        // Verify relayout runs only once when status bar inset visibility changes.
-        verify(decoration, times(1)).relayout(task)
+        verify(decoration1).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+        verify(decoration2).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+        verify(decoration3).onKeyguardStateChanged(true /* visible */, true /* occluded */)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 77337a0..d8f395d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,7 +56,7 @@
 public class DragResizeWindowGeometryTests extends ShellTestCase {
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
-    private static final int EDGE_RESIZE_THICKNESS = 15;
+    private static final int EDGE_RESIZE_THICKNESS = 12;
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2ec3ab5..6154391c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -31,6 +31,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
@@ -63,6 +64,7 @@
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
@@ -158,6 +160,8 @@
         mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
         mRelayoutParams.mCornerRadius = CORNER_RADIUS;
 
+        when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
+                .thenReturn(mock(Display.class));
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
         when(mMockSurfaceControlViewHost.getRootSurfaceControl())
@@ -629,15 +633,15 @@
                 .setVisible(true)
                 .setBounds(new Rect(0, 0, 1000, 1000))
                 .build();
+        taskInfo.isFocused = true;
+        // Caption visible at first.
+        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
-        // Run it once so that insets are added.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
         windowDecor.relayout(taskInfo);
 
-        // Run it again so that insets are removed.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
-        windowDecor.relayout(taskInfo);
+        // Hide caption so insets are removed.
+        windowDecor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
 
         verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
                 eq(0) /* index */, eq(captionBar()));
@@ -656,10 +660,10 @@
                 .setVisible(true)
                 .setBounds(new Rect(0, 0, 1000, 1000))
                 .build();
-        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
         // Hidden from the beginning, so no insets were ever added.
-        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+        when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+                .thenReturn(createInsetsState(statusBars(), false /* visible */));
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
         windowDecor.relayout(taskInfo);
 
         // Never added.
@@ -896,6 +900,78 @@
         windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
     }
 
+    @Test
+    public void onStatusBarVisibilityChange_shownToHidden_hidesCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertTrue(decor.mIsCaptionVisible);
+
+        decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
+
+        assertFalse(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onStatusBarVisibilityChange_hiddenToShown_showsCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), false /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertFalse(decor.mIsCaptionVisible);
+
+        decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
+
+        assertTrue(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onKeyguardStateChange_hiddenToShownAndOccluding_hidesCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.relayout(task);
+        assertTrue(decor.mIsCaptionVisible);
+
+        decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+
+        assertFalse(decor.mIsCaptionVisible);
+    }
+
+    @Test
+    public void onKeyguardStateChange_showingAndOccludingToHidden_showsCaption() {
+        final ActivityManager.RunningTaskInfo task = createTaskInfo();
+        when(mMockDisplayController.getInsetsState(task.displayId))
+                .thenReturn(createInsetsState(statusBars(), true /* visible */));
+        final TestWindowDecoration decor = createWindowDecoration(task);
+        decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+        assertFalse(decor.mIsCaptionVisible);
+
+        decor.onKeyguardStateChanged(false /* visible */, false /* occluding */);
+
+        assertTrue(decor.mIsCaptionVisible);
+    }
+
+    private ActivityManager.RunningTaskInfo createTaskInfo() {
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setVisible(true)
+                .build();
+        taskInfo.isFocused = true;
+        return taskInfo;
+    }
+
+    private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) {
+        final InsetsState state = new InsetsState();
+        final InsetsSource source = new InsetsSource(0, type);
+        source.setVisible(visible);
+        state.addSource(source);
+        return state;
+    }
+
     private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
                 mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
@@ -961,10 +1037,24 @@
             return null;
         }
 
+        @Override
+        int getCaptionViewId() {
+            return R.id.caption;
+        }
+
+        @Override
+        TestView inflateLayout(Context context, int layoutResId) {
+            if (layoutResId == R.layout.caption_layout) {
+                return mMockView;
+            }
+            return super.inflateLayout(context, layoutResId);
+        }
+
         void relayout(ActivityManager.RunningTaskInfo taskInfo,
                 boolean applyStartTransactionOnDraw) {
             mRelayoutParams.mRunningTaskInfo = taskInfo;
             mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+            mRelayoutParams.mLayoutResId = R.layout.caption_layout;
             relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
                     mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
diff --git a/media/jni/JetPlayer.h b/media/jni/JetPlayer.h
index bb569bc..4cc266d 100644
--- a/media/jni/JetPlayer.h
+++ b/media/jni/JetPlayer.h
@@ -40,7 +40,7 @@
     static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
     static const int JET_PAUSE_UPDATE            = 4;
 
-    JetPlayer(void *javaJetPlayer,
+    explicit JetPlayer(void *javaJetPlayer,
             int maxTracks = 32,
             int trackBufferSize = 1200);
     ~JetPlayer();
@@ -69,7 +69,6 @@
     void                fireUpdateOnStatusChange();
     void                fireEventsFromJetQueue();
 
-    JetPlayer() {} // no default constructor
     void dump();
     void dumpJetStatus(S_JET_STATUS* pJetStatus);
 
@@ -96,7 +95,7 @@
 
     class JetPlayerThread : public Thread {
     public:
-        JetPlayerThread(JetPlayer *player) : mPlayer(player) {
+        explicit JetPlayerThread(JetPlayer *player) : mPlayer(player) {
         }
 
     protected:
@@ -106,8 +105,7 @@
         JetPlayer *mPlayer;
 
         bool threadLoop() {
-            int result;
-            result = mPlayer->render();
+            mPlayer->render();
             return false;
         }
 
diff --git a/native/android/surface_control_input_receiver.cpp b/native/android/surface_control_input_receiver.cpp
index a84ec73..7caa943 100644
--- a/native/android/surface_control_input_receiver.cpp
+++ b/native/android/surface_control_input_receiver.cpp
@@ -41,7 +41,7 @@
                   const sp<IBinder>& clientToken, const sp<InputTransferToken>& inputTransferToken,
                   AInputReceiverCallbacks* callbacks)
           : mCallbacks(callbacks),
-            mInputConsumer(inputChannel, looper, *this),
+            mInputConsumer(inputChannel, looper, *this, nullptr),
             mClientToken(clientToken),
             mInputTransferToken(inputTransferToken) {}
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..1e48443
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/non_collapsing_toolbar_content_layout"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..33519cb
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true"
+        android:outlineAmbientShadowColor="@android:color/transparent"
+        android:outlineSpotShadowColor="@android:color/transparent"
+        android:background="@android:color/transparent"
+        android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+        <Toolbar
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:theme="?android:attr/actionBarTheme"
+            android:transitionName="shared_element_view"
+            app:layout_collapseMode="pin"/>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/content_frame"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index 4659051..f46f110 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -171,7 +171,7 @@
 
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
-            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
         }
         return mToolbardelegate;
     }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 3965303..16ed5a8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -169,7 +169,7 @@
 
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
-            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
         }
         return mToolbardelegate;
     }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
index b605074..da97c30 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
@@ -57,7 +57,8 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+        mToolbardelegate =
+                new CollapsingToolbarDelegate(new DelegateCallback(), useCollapsingToolbar());
     }
 
     @Nullable
@@ -98,4 +99,8 @@
     public FrameLayout getContentFrameLayout() {
         return mToolbardelegate.getContentFrameLayout();
     }
+
+    protected boolean useCollapsingToolbar() {
+        return true;
+    }
 }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index b633337..2ab2abd 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -21,6 +21,8 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
 import android.util.Log;
@@ -80,8 +82,12 @@
     @NonNull
     private final HostCallback mHostCallback;
 
-    public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback) {
+    private boolean mUseCollapsingToolbar;
+
+    public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback,
+            boolean useCollapsingToolbar) {
         mHostCallback = hostCallback;
+        mUseCollapsingToolbar = useCollapsingToolbar;
     }
 
     /** Method to call that creates the root view of the collapsing toolbar. */
@@ -94,13 +100,32 @@
     @SuppressWarnings("RestrictTo")
     View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             Activity activity) {
-        final View view =
-                inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
+        int layoutId;
+        boolean useCollapsingToolbar =
+                mUseCollapsingToolbar || Build.VERSION.SDK_INT < Build.VERSION_CODES.S;
+        if (useCollapsingToolbar) {
+            layoutId = R.layout.collapsing_toolbar_base_layout;
+        } else {
+            layoutId = R.layout.non_collapsing_toolbar_base_layout;
+        }
+        final View view = inflater.inflate(layoutId, container, false);
         if (view instanceof CoordinatorLayout) {
             mCoordinatorLayout = (CoordinatorLayout) view;
         }
         mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
         mAppBarLayout = view.findViewById(R.id.app_bar);
+
+        if (!useCollapsingToolbar) {
+            // In the non-collapsing toolbar layout, we need to set the background of the app bar to
+            // the same as the activity background so that it covers the items extending above the
+            // bounds of the list for edge-to-edge.
+            TypedArray ta = container.getContext().obtainStyledAttributes(new int[] {
+                    android.R.attr.windowBackground});
+            Drawable background = ta.getDrawable(0);
+            ta.recycle();
+            mAppBarLayout.setBackground(background);
+        }
+
         if (mCollapsingToolbarLayout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 0fec61c..92da2be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1026,21 +1026,29 @@
         return mDevice.getBluetoothClass();
     }
 
+    /**
+     * Returns a list of {@link LocalBluetoothProfile} supported by the device.
+     */
     public List<LocalBluetoothProfile> getProfiles() {
         return new ArrayList<>(mProfiles);
     }
 
-    public List<LocalBluetoothProfile> getConnectableProfiles() {
-        List<LocalBluetoothProfile> connectableProfiles =
-                new ArrayList<LocalBluetoothProfile>();
+    /**
+     * Returns a list of {@link LocalBluetoothProfile} that are user-accessible from UI to
+     * initiate a connection.
+     *
+     * Note: Use {@link #getProfiles()} to retrieve all supported profiles on the device.
+     */
+    public List<LocalBluetoothProfile> getUiAccessibleProfiles() {
+        List<LocalBluetoothProfile> accessibleProfiles = new ArrayList<>();
         synchronized (mProfileLock) {
             for (LocalBluetoothProfile profile : mProfiles) {
                 if (profile.accessProfileEnabled()) {
-                    connectableProfiles.add(profile);
+                    accessibleProfiles.add(profile);
                 }
             }
         }
-        return connectableProfiles;
+        return accessibleProfiles;
     }
 
     public List<LocalBluetoothProfile> getRemovedProfiles() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index a49314a..7124ed2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -261,9 +261,9 @@
         }
 
         CachedBluetoothDevice dualModeDevice = groupDevicesList.stream()
-                .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+                .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
                         .anyMatch(profile -> profile instanceof LeAudioProfile))
-                .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+                .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
                         .anyMatch(profile -> profile instanceof A2dpProfile
                                 || profile instanceof HeadsetProfile))
                 .findFirst().orElse(null);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 9ff5c43..326bb31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -19,37 +19,39 @@
 import android.bluetooth.BluetoothAdapter
 import android.content.Context
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
 import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
-import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
-import java.util.concurrent.ConcurrentHashMap
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
+import com.google.common.cache.LoadingCache
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 /** Provides functionality to control bluetooth device settings. */
 interface DeviceSettingRepository {
     /** Gets config for the bluetooth device, returns null if failed. */
-    suspend fun getDeviceSettingsConfig(cachedDevice: CachedBluetoothDevice): DeviceSettingsConfig?
-
-    /** Gets all device settings for the bluetooth device. */
-    fun getDeviceSettingList(
-        cachedDevice: CachedBluetoothDevice,
-    ): Flow<List<DeviceSetting>?>
+    suspend fun getDeviceSettingsConfig(
+        cachedDevice: CachedBluetoothDevice
+    ): DeviceSettingConfigModel?
 
     /** Gets device setting for the bluetooth device. */
     fun getDeviceSetting(
         cachedDevice: CachedBluetoothDevice,
         @DeviceSettingId settingId: Int
-    ): Flow<DeviceSetting?>
-
-    /** Updates device setting for the bluetooth device. */
-    suspend fun updateDeviceSettingState(
-        cachedDevice: CachedBluetoothDevice,
-        @DeviceSettingId deviceSettingId: Int,
-        deviceSettingPreferenceState: DeviceSettingPreferenceState,
-    )
+    ): Flow<DeviceSettingModel?>
 }
 
 class DeviceSettingRepositoryImpl(
@@ -58,40 +60,94 @@
     private val coroutineScope: CoroutineScope,
     private val backgroundCoroutineContext: CoroutineContext,
 ) : DeviceSettingRepository {
-    private val deviceSettings =
-        ConcurrentHashMap<CachedBluetoothDevice, DeviceSettingServiceConnection>()
+    private val connectionCache:
+        LoadingCache<CachedBluetoothDevice, DeviceSettingServiceConnection> =
+        CacheBuilder.newBuilder()
+            .weakValues()
+            .build(
+                object : CacheLoader<CachedBluetoothDevice, DeviceSettingServiceConnection>() {
+                    override fun load(
+                        cachedDevice: CachedBluetoothDevice
+                    ): DeviceSettingServiceConnection =
+                        DeviceSettingServiceConnection(
+                            cachedDevice,
+                            context,
+                            bluetoothAdaptor,
+                            coroutineScope,
+                            backgroundCoroutineContext,
+                        )
+                }
+            )
 
     override suspend fun getDeviceSettingsConfig(
         cachedDevice: CachedBluetoothDevice
-    ): DeviceSettingsConfig? = createConnectionIfAbsent(cachedDevice).getDeviceSettingsConfig()
-
-    override fun getDeviceSettingList(
-        cachedDevice: CachedBluetoothDevice
-    ): Flow<List<DeviceSetting>?> = createConnectionIfAbsent(cachedDevice).getDeviceSettingList()
+    ): DeviceSettingConfigModel? =
+        connectionCache.get(cachedDevice).getDeviceSettingsConfig()?.toModel()
 
     override fun getDeviceSetting(
         cachedDevice: CachedBluetoothDevice,
         settingId: Int
-    ): Flow<DeviceSetting?> = createConnectionIfAbsent(cachedDevice).getDeviceSetting(settingId)
-
-    override suspend fun updateDeviceSettingState(
-        cachedDevice: CachedBluetoothDevice,
-        @DeviceSettingId deviceSettingId: Int,
-        deviceSettingPreferenceState: DeviceSettingPreferenceState,
-    ) =
-        createConnectionIfAbsent(cachedDevice)
-            .updateDeviceSettings(deviceSettingId, deviceSettingPreferenceState)
-
-    private fun createConnectionIfAbsent(
-        cachedDevice: CachedBluetoothDevice
-    ): DeviceSettingServiceConnection =
-        deviceSettings.computeIfAbsent(cachedDevice) {
-            DeviceSettingServiceConnection(
-                cachedDevice,
-                context,
-                bluetoothAdaptor,
-                coroutineScope,
-                backgroundCoroutineContext,
-            )
+    ): Flow<DeviceSettingModel?> =
+        connectionCache.get(cachedDevice).let { connection ->
+            connection.getDeviceSetting(settingId).map { it?.toModel(cachedDevice, connection) }
         }
+
+    private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
+        DeviceSettingConfigModel(
+            mainItems = mainContentItems.map { it.toModel() },
+            moreSettingsItems = moreSettingsItems.map { it.toModel() },
+            moreSettingsPageFooter = moreSettingsFooter
+        )
+
+    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel =
+        DeviceSettingConfigItemModel(settingId)
+
+    private fun DeviceSetting.toModel(
+        cachedDevice: CachedBluetoothDevice,
+        connection: DeviceSettingServiceConnection
+    ): DeviceSettingModel =
+        when (val pref = preference) {
+            is ActionSwitchPreference ->
+                DeviceSettingModel.ActionSwitchPreference(
+                    cachedDevice = cachedDevice,
+                    id = settingId,
+                    title = pref.title,
+                    summary = pref.summary,
+                    icon = pref.icon,
+                    isAllowedChangingState = pref.isAllowedChangingState,
+                    intent = pref.intent,
+                    switchState =
+                        if (pref.hasSwitch()) {
+                            DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
+                        } else {
+                            null
+                        },
+                    updateState = { newState ->
+                        coroutineScope.launch(backgroundCoroutineContext) {
+                            connection.updateDeviceSettings(
+                                settingId,
+                                newState.toParcelable(),
+                            )
+                        }
+                    },
+                )
+            is MultiTogglePreference ->
+                DeviceSettingModel.MultiTogglePreference(
+                    cachedDevice = cachedDevice,
+                    id = settingId,
+                    title = pref.title,
+                    toggles = pref.toggleInfos.map { it.toModel() },
+                    isAllowedChangingState = pref.isAllowedChangingState,
+                    isActive = true,
+                    state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state),
+                    updateState = { newState ->
+                        coroutineScope.launch(backgroundCoroutineContext) {
+                            connection.updateDeviceSettings(settingId, newState.toParcelable())
+                        }
+                    },
+                )
+            else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
+        }
+
+    private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
new file mode 100644
index 0000000..cd597ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.shared.model
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+
+/** Models a device setting config. */
+data class DeviceSettingConfigModel(
+    /** Items need to be shown in device details main page. */
+    val mainItems: List<DeviceSettingConfigItemModel>,
+    /** Items need to be shown in device details more settings page. */
+    val moreSettingsItems: List<DeviceSettingConfigItemModel>,
+    /** Footer text in more settings page. */
+    val moreSettingsPageFooter: String)
+
+/** Models a device setting item in config. */
+data class DeviceSettingConfigItemModel(
+    @DeviceSettingId val settingId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 72a60fb..fe6659d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -634,7 +634,7 @@
         }
 
         private boolean isMediaDevice(CachedBluetoothDevice device) {
-            for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
+            for (LocalBluetoothProfile profile : device.getUiAccessibleProfiles()) {
                 if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
                         profile instanceof LeAudioProfile) {
                     return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 22e6133..2c982d6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -40,13 +40,18 @@
     private ZenModeConfig.ZenRule mConfigZenRule;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
-    public static final ZenMode MANUAL_DND = ZenMode.manualDndMode(
-            new AutomaticZenRule.Builder("Manual DND", Uri.parse("rule://dnd"))
+    public static final ZenMode MANUAL_DND_ACTIVE = ZenMode.manualDndMode(
+            new AutomaticZenRule.Builder("Do Not Disturb", Uri.parse("rule://dnd"))
                     .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
                     .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
                     .build(),
-            true /* isActive */
-    );
+            /* isActive= */ true);
+    public static final ZenMode MANUAL_DND_INACTIVE = ZenMode.manualDndMode(
+            new AutomaticZenRule.Builder("Do Not Disturb", Uri.parse("rule://dnd"))
+                    .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                    .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+                    .build(),
+            /* isActive= */ false);
 
     public TestModeBuilder() {
         // Reasonable defaults
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index 3f59da4..f94f21f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -145,18 +145,18 @@
         profiles.add(mHfpProfile);
         profiles.add(mA2dpProfile);
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice1.isConnected()).thenReturn(true);
 
         profiles.clear();
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice2.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice2.isConnected()).thenReturn(true);
 
         profiles.clear();
         profiles.add(mHfpProfile);
         profiles.add(mA2dpProfile);
-        when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice3.getUiAccessibleProfiles()).thenReturn(profiles);
         when(mCachedDevice3.isConnected()).thenReturn(true);
     }
 
@@ -253,7 +253,7 @@
         when(mDevice2.isConnected()).thenReturn(false);
         List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
         profiles.add(mLeAudioProfile);
-        when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+        when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
         CachedBluetoothDevice expectedDevice = mCachedDevice1;
 
         assertThat(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index b5457c5..fee2394 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.ServiceConnection
+import android.graphics.Bitmap
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
@@ -34,6 +35,14 @@
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceState
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
@@ -148,7 +157,7 @@
 
             val config = underTest.getDeviceSettingsConfig(cachedDevice)
 
-            assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+            assertConfig(config!!, DEVICE_SETTING_CONFIG)
         }
     }
 
@@ -163,7 +172,7 @@
                 )
                 .thenReturn("".toByteArray())
 
-            var config: DeviceSettingsConfig? = null
+            var config: DeviceSettingConfigModel? = null
             val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
             delay(1000)
             verify(bluetoothAdapter)
@@ -185,7 +194,7 @@
                 .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
 
             job.join()
-            assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+            assertConfig(config!!, DEVICE_SETTING_CONFIG)
         }
     }
 
@@ -202,7 +211,7 @@
     }
 
     @Test
-    fun getDeviceSettingList_success() {
+    fun getDeviceSetting_actionSwitchPreference_success() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
             `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -211,73 +220,7 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
-            `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
-            }
-            var settings: List<DeviceSetting>? = null
-
-            underTest
-                .getDeviceSettingList(cachedDevice)
-                .onEach { settings = it }
-                .launchIn(backgroundScope)
-            runCurrent()
-
-            assertThat(settings?.map { it.settingId })
-                .containsExactly(
-                    DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                    DeviceSettingId.DEVICE_SETTING_ID_ANC
-                )
-            assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
-                .containsExactly(
-                    "title1",
-                    "title2",
-                )
-        }
-    }
-
-    @Test
-    fun getDeviceSetting_oneServiceFailed_returnPartialResult() {
-        testScope.runTest {
-            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
-            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
-            }
-            var settings: List<DeviceSetting>? = null
-
-            underTest
-                .getDeviceSettingList(cachedDevice)
-                .onEach { settings = it }
-                .launchIn(backgroundScope)
-            runCurrent()
-
-            assertThat(settings?.map { it.settingId })
-                .containsExactly(
-                    DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                )
-            assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
-                .containsExactly(
-                    "title1",
-                )
-        }
-    }
-
-    @Test
-    fun getDeviceSetting_success() {
-        testScope.runTest {
-            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
-            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
-                input ->
-                input
-                    .getArgument<IDeviceSettingsListener>(1)
-                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
-            }
-            var setting: DeviceSetting? = null
+            var setting: DeviceSettingModel? = null
 
             underTest
                 .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -285,13 +228,55 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            assertThat(setting?.settingId).isEqualTo(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
-            assertThat((setting?.preference as ActionSwitchPreference).title).isEqualTo("title1")
+            assertDeviceSetting(setting!!, DEVICE_SETTING_1)
         }
     }
 
     @Test
-    fun updateDeviceSetting_success() {
+    fun getDeviceSetting_multiTogglePreference_success() {
+        testScope.runTest {
+            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+            `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+                input ->
+                input
+                    .getArgument<IDeviceSettingsListener>(1)
+                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            assertDeviceSetting(setting!!, DEVICE_SETTING_2)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_noConfig_returnNull() {
+        testScope.runTest {
+            `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+                input ->
+                input
+                    .getArgument<IDeviceSettingsListener>(1)
+                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            assertThat(setting).isNull()
+        }
+    }
+
+    @Test
+    fun updateDeviceSettingState_switchState_success() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
             `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -300,12 +285,15 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
+            var setting: DeviceSettingModel? = null
 
-            underTest.updateDeviceSettingState(
-                cachedDevice,
-                DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                ActionSwitchPreferenceState.Builder().build()
-            )
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+            val updateFunc = (setting as DeviceSettingModel.ActionSwitchPreference).updateState!!
+            updateFunc(DeviceSettingStateModel.ActionSwitchPreferenceState(false))
             runCurrent()
 
             verify(settingProviderService1)
@@ -313,12 +301,107 @@
                     DEVICE_INFO,
                     DeviceSettingState.Builder()
                         .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
-                        .setPreferenceState(ActionSwitchPreferenceState.Builder().build())
+                        .setPreferenceState(
+                            ActionSwitchPreferenceState.Builder().setChecked(false).build()
+                        )
                         .build()
                 )
         }
     }
 
+    @Test
+    fun updateDeviceSettingState_multiToggleState_success() {
+        testScope.runTest {
+            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+            `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+                input ->
+                input
+                    .getArgument<IDeviceSettingsListener>(1)
+                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+            val updateFunc = (setting as DeviceSettingModel.MultiTogglePreference).updateState
+            updateFunc(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+            runCurrent()
+
+            verify(settingProviderService2)
+                .updateDeviceSettings(
+                    DEVICE_INFO,
+                    DeviceSettingState.Builder()
+                        .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
+                        .setPreferenceState(
+                            MultiTogglePreferenceState.Builder().setState(2).build()
+                        )
+                        .build()
+                )
+        }
+    }
+
+    private fun assertDeviceSetting(actual: DeviceSettingModel, serviceResponse: DeviceSetting) {
+        assertThat(actual.id).isEqualTo(serviceResponse.settingId)
+        when (actual) {
+            is DeviceSettingModel.ActionSwitchPreference -> {
+                assertThat(serviceResponse.preference)
+                    .isInstanceOf(ActionSwitchPreference::class.java)
+                val pref = serviceResponse.preference as ActionSwitchPreference
+                assertThat(actual.title).isEqualTo(pref.title)
+                assertThat(actual.summary).isEqualTo(pref.summary)
+                assertThat(actual.icon).isEqualTo(pref.icon)
+                assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+                if (pref.hasSwitch()) {
+                    assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
+                } else {
+                    assertThat(actual.switchState).isNull()
+                }
+            }
+            is DeviceSettingModel.MultiTogglePreference -> {
+                assertThat(serviceResponse.preference)
+                    .isInstanceOf(MultiTogglePreference::class.java)
+                val pref = serviceResponse.preference as MultiTogglePreference
+                assertThat(actual.title).isEqualTo(pref.title)
+                assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+                assertThat(actual.toggles.size).isEqualTo(pref.toggleInfos.size)
+                for (i in 0..<actual.toggles.size) {
+                    assertToggle(actual.toggles[i], pref.toggleInfos[i])
+                }
+            }
+            else -> {}
+        }
+    }
+
+    private fun assertToggle(actual: ToggleModel, serviceResponse: ToggleInfo) {
+        assertThat(actual.label).isEqualTo(serviceResponse.label)
+        assertThat(actual.icon).isEqualTo(serviceResponse.icon)
+    }
+
+    private fun assertConfig(
+        actual: DeviceSettingConfigModel,
+        serviceResponse: DeviceSettingsConfig
+    ) {
+        assertThat(actual.mainItems.size).isEqualTo(serviceResponse.mainContentItems.size)
+        for (i in 0..<actual.mainItems.size) {
+            assertConfigItem(actual.mainItems[i], serviceResponse.mainContentItems[i])
+        }
+        assertThat(actual.moreSettingsItems.size).isEqualTo(serviceResponse.moreSettingsItems.size)
+        for (i in 0..<actual.moreSettingsItems.size) {
+            assertConfigItem(actual.moreSettingsItems[i], serviceResponse.moreSettingsItems[i])
+        }
+        assertThat(actual.moreSettingsPageFooter).isEqualTo(serviceResponse.moreSettingsFooter)
+    }
+
+    private fun assertConfigItem(
+        actual: DeviceSettingConfigItemModel,
+        serviceResponse: DeviceSettingItem
+    ) {
+        assertThat(actual.settingId).isEqualTo(serviceResponse.settingId)
+    }
+
     private companion object {
         const val BLUETOOTH_ADDRESS = "12:34:56:78"
         const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
@@ -377,10 +460,21 @@
             DeviceSetting.Builder()
                 .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
                 .setPreference(
-                    ActionSwitchPreference.Builder()
-                        .setTitle("title2")
-                        .setHasSwitch(true)
-                        .setAllowedChangingState(true)
+                    MultiTogglePreference.Builder()
+                        .setTitle("title1")
+                        .setAllowChangingState(true)
+                        .addToggleInfo(
+                            ToggleInfo.Builder()
+                                .setLabel("label1")
+                                .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+                                .build()
+                        )
+                        .addToggleInfo(
+                            ToggleInfo.Builder()
+                                .setLabel("label2")
+                                .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+                                .build()
+                        )
                         .build()
                 )
                 .build()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a30d6a7..3e8457b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -470,7 +470,7 @@
         when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(cachedDevice.isConnected()).thenReturn(false);
-        when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
+        when(cachedDevice.getUiAccessibleProfiles()).thenReturn(profiles);
         when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
         when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index f53dec6..b1e6d66 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -28,6 +28,13 @@
 }
 
 flag {
+    name: "use_new_storage_value"
+    namespace: "core_experiments_team_internal"
+    description: "When enabled, read the new storage value in aconfig codegen, and actually use it."
+    bug: "312235596"
+}
+
+flag {
     name: "load_apex_aconfig_protobufs"
     namespace: "core_experiments_team_internal"
     description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8c96484..e66bacf 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -677,6 +677,7 @@
                  Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
                  Settings.Secure.CARRIER_APPS_HANDLED,
                  Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+                 Settings.Secure.COMPAT_UI_EDUCATION_SHOWING,
                  Settings.Secure.COMPLETED_CATEGORY_PREFIX,
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3767a27..19dced5 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -475,18 +475,6 @@
 }
 
 flag {
-   name: "centralized_status_bar_height_fix"
-   namespace: "systemui"
-   description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
-        " central place, instead of reading resources directly. This is to take into account display"
-        " cutouts and other special cases. "
-   bug: "317016114"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
-
-flag {
   name: "enable_layout_tracing"
   namespace: "systemui"
   description: "Enables detailed traversal slices during measure and layout in perfetto traces"
@@ -1272,4 +1260,14 @@
    namespace: "systemui"
    description: "Adding haptic component infrastructure to sliders in Compose."
    bug: "341968766"
+}
+
+flag {
+  namespace: "systemui"
+  name: "settings_ext_register_content_observer_on_bg_thread"
+  description: "Register content observer in callback flow APIs on background thread in SettingsProxyExt."
+  bug: "355389014"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
new file mode 100644
index 0000000..d8c7c06
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/**
+ * Checks if the synchronous APIs like registerContentObserverSync/unregisterContentObserverSync are
+ * invoked for SettingsProxy or it's sub-classes, and raise a warning notifying the caller to use
+ * the asynchronous/suspend APIs instead.
+ */
+@Suppress("UnstableApiUsage")
+class RegisterContentObserverSyncViaSettingsProxyDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return SYNC_METHOD_LIST
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+
+        val evaluator = context.evaluator
+        if (evaluator.isMemberInSubClassOf(method, SETTINGS_PROXY_CLASS)) {
+            context.report(
+                issue = SYNC_WARNING,
+                location = context.getNameLocation(node),
+                message =
+                    "`Avoid using ${method.name}()` if calling the API is not " +
+                        "required on the main thread. Instead use an appropriate async interface " +
+                        "API call for eg. `registerContentObserver()` or " +
+                        "`registerContentObserverAsync()`."
+            )
+        }
+    }
+
+    companion object {
+        val SYNC_WARNING: Issue =
+            Issue.create(
+                id = "RegisterContentObserverSyncWarning",
+                briefDescription =
+                    "Synchronous content observer registration API called " +
+                        "instead of the async APIs.`",
+                // lint trims indents and converts \ to line continuations
+                explanation =
+                    """
+                        ContentObserver registration/de-registration done via \
+                        `SettingsProxy.registerContentObserverSync` will block the main thread \
+                        and may cause missed frames. Instead, use \
+                        `SettingsProxy.registerContentObserver()` or \
+                        `SettingsProxy.registerContentObserverAsync()`. These APIs will ensure \
+                        that the registrations/de-registrations happen sequentially on a
+                        background worker thread.""",
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(
+                        RegisterContentObserverSyncViaSettingsProxyDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+
+        private val SYNC_METHOD_LIST =
+            listOf(
+                "registerContentObserverSync",
+                "unregisterContentObserverSync",
+                "registerContentObserverForUserSync"
+            )
+
+        private val SETTINGS_PROXY_CLASS = "com.android.systemui.util.settings.SettingsProxy"
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 73ac6cc..5206b05 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -46,10 +46,12 @@
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
                 MissingApacheLicenseDetector.ISSUE,
+                RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING
             )
 
     override val api: Int
         get() = CURRENT_API
+
     override val minApi: Int
         get() = 8
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
new file mode 100644
index 0000000..57347d3
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test class for [RegisterContentObserverSyncViaSettingsProxyDetector]. */
+class RegisterContentObserverSyncViaSettingsProxyDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector = RegisterContentObserverSyncViaSettingsProxyDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+
+    @Test
+    fun testRegisterContentObserverSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testRegisterContentObserverForUserSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverForUserSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testSuppressRegisterContentObserverSync() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        @SuppressWarnings("RegisterContentObserverSyncWarning")
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                                false, mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNoopIfNoCall() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testUnRegisterContentObserverSync_throwError() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import com.android.systemui.util.settings.SecureSettings;
+                    public class TestClass {
+                        public void register(SecureSettings secureSettings) {
+                          secureSettings.
+                            unregisterContentObserverSync(mSettingObserver);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+            .run()
+            .expect(
+                """
+        src/test/pkg/TestClass.java:6: Warning: Avoid using unregisterContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+        unregisterContentObserverSync(mSettingObserver);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    private companion object {
+        private val SETTINGS_PROXY_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface SettingsProxy {
+                fun registerContentObserverSync() {}
+                fun unregisterContentObserverSync() {}
+            }
+            """
+                )
+                .indented()
+
+        private val USER_SETTINGS_PROXY_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface UserSettingsProxy : SettingsProxy {
+                fun registerContentObserverForUserSync() {}
+            }
+            """
+                )
+                .indented()
+
+        private val SECURE_SETTINGS_STUB =
+            kotlin(
+                    """
+            package com.android.systemui.util.settings
+            interface SecureSettings : UserSettingsProxy {}
+            """
+                )
+                .indented()
+    }
+
+    private val stubs = arrayOf(SETTINGS_PROXY_STUB, USER_SETTINGS_PROXY_STUB, SECURE_SETTINGS_STUB)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f0f407a..4e117d6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -23,19 +23,16 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
-import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -97,11 +94,7 @@
         }
 
         val splitShadeTopMargin: Dp =
-            if (Flags.centralizedStatusBarHeightFix()) {
-                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
-            } else {
-                dimensionResource(id = R.dimen.large_screen_shade_header_height)
-            }
+            LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
 
         ConstrainedNotificationStack(
             stackScrollView = stackScrollView.get(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 40ea0a0..460461a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -37,7 +37,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index fa47a02..4e1f82c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,7 +37,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 9c9ee53..b99dec4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -37,7 +37,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class ColorInversionRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
index c0d481c..1378dac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -35,7 +35,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class OneHandedModeRepositoryImplTest : SysuiTestCase() {
 
     private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ed3b4c0..ce22e28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -31,7 +31,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
     private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
index 9cfa572..667d364 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
@@ -35,7 +35,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class CameraAutoRotateRepositoryImplTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index 2911a50..c37b33e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -40,7 +40,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
 class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
new file mode 100644
index 0000000..50fdb31
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityControllerTest : SysuiTestCase() {
+    @Test
+    fun activityLifecycle_stoppedWhenNotWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_notStoppedWhenNotWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity, never()).finish()
+    }
+
+    @Test
+    fun activityLifecycle_stoppedAfterResultReturned() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityController(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        controller.onWaitingForResult(false)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_statePreservedThroughInstanceSave() {
+        val activity = mock<Activity>()
+        val bundle = Bundle(1)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityController(activity)
+            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+            controller.onWaitingForResult(true)
+            callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
+        }
+
+        clearInvocations(activity)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityController(activity)
+            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+            callbackCapture.lastValue.onActivityCreated(activity, bundle)
+            callbackCapture.lastValue.onActivityStopped(activity)
+
+            verify(activity, never()).finish()
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 331db52..5dd6c22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -21,15 +21,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.google.common.truth.Truth.assertThat
 import java.io.File
-import java.time.Clock
-import java.time.Instant
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.test.TestScope
@@ -44,13 +43,13 @@
 @RunWith(AndroidJUnit4::class)
 class ContextualEducationRepositoryTest : SysuiTestCase() {
 
-    private lateinit var underTest: ContextualEducationRepository
+    private lateinit var underTest: UserContextualEducationRepository
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
     private val dsScopeProvider: Provider<CoroutineScope> = Provider {
         TestScope(kosmos.testDispatcher).backgroundScope
     }
-    private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
+
     private val testUserId = 1111
 
     // For deleting any test files created after the test
@@ -61,8 +60,7 @@
         // Create TestContext here because TemporaryFolder.create() is called in @Before. It is
         // needed before calling TemporaryFolder.newFolder().
         val testContext = TestContext(context, tmpFolder.newFolder())
-        val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
-        underTest = ContextualEducationRepositoryImpl(clock, userRepository)
+        underTest = UserContextualEducationRepository(testContext, dsScopeProvider)
         underTest.setUser(testUserId)
     }
 
@@ -70,7 +68,7 @@
     fun changeRetrievedValueForNewUser() =
         testScope.runTest {
             // Update data for old user.
-            underTest.incrementSignalCount(BACK)
+            underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
             assertThat(model?.signalCount).isEqualTo(1)
 
@@ -81,20 +79,19 @@
         }
 
     @Test
-    fun incrementSignalCount() =
+    fun dataChangedOnUpdate() =
         testScope.runTest {
-            underTest.incrementSignalCount(BACK)
+            val newModel =
+                GestureEduModel(
+                    signalCount = 2,
+                    educationShownCount = 1,
+                    lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+                    lastEducationTime = kosmos.fakeEduClock.instant(),
+                    usageSessionStartTime = kosmos.fakeEduClock.instant(),
+                )
+            underTest.updateGestureEduModel(BACK) { newModel }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
-            assertThat(model?.signalCount).isEqualTo(1)
-        }
-
-    @Test
-    fun dataAddedOnUpdateShortcutTriggerTime() =
-        testScope.runTest {
-            val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
-            assertThat(model?.lastShortcutTriggeredTime).isNull()
-            underTest.updateShortcutTriggerTime(BACK)
-            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+            assertThat(model).isEqualTo(newModel)
         }
 
     /** Test context which allows overriding getFilesDir path */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index ae3302c..6867089 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -19,13 +19,18 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.education.shared.model.EducationUiType
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -36,8 +41,9 @@
 class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.contextualEducationRepository
+    private val contextualEduInteractor = kosmos.contextualEducationInteractor
     private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+    private val eduClock = kosmos.fakeEduClock
 
     @Before
     fun setup() {
@@ -47,15 +53,35 @@
     @Test
     fun newEducationInfoOnMaxSignalCountReached() =
         testScope.runTest {
-            tryTriggeringEducation(BACK)
+            triggerMaxEducationSignals(BACK)
             val model by collectLastValue(underTest.educationTriggered)
             assertThat(model?.gestureType).isEqualTo(BACK)
         }
 
     @Test
+    fun newEducationToastOn1stEducation() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.educationTriggered)
+            triggerMaxEducationSignals(BACK)
+            assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
+        }
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun newEducationNotificationOn2ndEducation() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.educationTriggered)
+            triggerMaxEducationSignals(BACK)
+            // runCurrent() to trigger 1st education
+            runCurrent()
+            triggerMaxEducationSignals(BACK)
+            assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
+        }
+
+    @Test
     fun noEducationInfoBeforeMaxSignalCountReached() =
         testScope.runTest {
-            repository.incrementSignalCount(BACK)
+            contextualEduInteractor.incrementSignalCount(BACK)
             val model by collectLastValue(underTest.educationTriggered)
             assertThat(model).isNull()
         }
@@ -64,15 +90,34 @@
     fun noEducationInfoWhenShortcutTriggeredPreviously() =
         testScope.runTest {
             val model by collectLastValue(underTest.educationTriggered)
-            repository.updateShortcutTriggerTime(BACK)
-            tryTriggeringEducation(BACK)
+            contextualEduInteractor.updateShortcutTriggerTime(BACK)
+            triggerMaxEducationSignals(BACK)
             assertThat(model).isNull()
         }
 
-    private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+    @Test
+    fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
+        testScope.runTest {
+            val model by
+                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+            contextualEduInteractor.incrementSignalCount(BACK)
+            eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
+            val secondSignalReceivedTime = eduClock.instant()
+            contextualEduInteractor.incrementSignalCount(BACK)
+
+            assertThat(model)
+                .isEqualTo(
+                    GestureEduModel(
+                        signalCount = 1,
+                        usageSessionStartTime = secondSignalReceivedTime
+                    )
+                )
+        }
+
+    private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
         // Increment max number of signal to try triggering education
         for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
-            repository.incrementSignalCount(gestureType)
+            contextualEduInteractor.incrementSignalCount(gestureType)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index d1f908d..46b370f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -16,16 +16,27 @@
 
 package com.android.systemui.lifecycle
 
+import android.view.View
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.Assert
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -110,4 +121,45 @@
 
         assertThat(isActive).isFalse()
     }
+
+    @Test
+    fun viewModel_viewBinder() = runTest {
+        Assert.setTestThread(Thread.currentThread())
+
+        val view: View = mock { on { isAttachedToWindow } doReturn false }
+        val viewModel = FakeViewModel()
+        backgroundScope.launch {
+            view.viewModel(
+                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                factory = { viewModel },
+            ) {
+                awaitCancellation()
+            }
+        }
+        runCurrent()
+
+        assertThat(viewModel.isActivated).isFalse()
+
+        view.stub { on { isAttachedToWindow } doReturn true }
+        argumentCaptor<View.OnAttachStateChangeListener>()
+            .apply { verify(view).addOnAttachStateChangeListener(capture()) }
+            .allValues
+            .forEach { it.onViewAttachedToWindow(view) }
+        runCurrent()
+
+        assertThat(viewModel.isActivated).isTrue()
+    }
+}
+
+private class FakeViewModel : SysUiViewModel() {
+    var isActivated = false
+
+    override suspend fun onActivated() {
+        isActivated = true
+        try {
+            awaitCancellation()
+        } finally {
+            isActivated = false
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index c3a5df06..661d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -67,19 +67,25 @@
             }
         }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun resize_updatesSharedPreferences() =
         with(kosmos) {
             testScope.runTest {
+                qsPreferencesRepository.setLargeTilesSpecs(setOf())
+                runCurrent()
+
                 val latest by collectLastValue(qsPreferencesRepository.largeTilesSpecs)
                 val spec = TileSpec.create("large")
 
                 // Assert that the tile is added to the large tiles after resizing
-                underTest.resize(spec, toIcon = false)
+                underTest.resize(spec)
+                runCurrent()
                 assertThat(latest).contains(spec)
 
                 // Assert that the tile is removed from the large tiles after resizing
-                underTest.resize(spec, toIcon = true)
+                underTest.resize(spec)
+                runCurrent()
                 assertThat(latest).doesNotContain(spec)
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 45262ca..b2f5765 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -37,15 +39,15 @@
     @Test
     fun isMoving_returnsCorrectValue() {
         // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
 
         // Start the drag movement
         underTest.onStarted(TestEditTiles[0])
 
         // Assert that the correct tile is marked as moving
         TestEditTiles.forEach {
-            assertThat(underTest.isMoving(it.tileSpec))
-                .isEqualTo(TestEditTiles[0].tileSpec == it.tileSpec)
+            assertThat(underTest.isMoving(it.tile.tileSpec))
+                .isEqualTo(TestEditTiles[0].tile.tileSpec == it.tile.tileSpec)
         }
     }
 
@@ -55,11 +57,11 @@
         underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tileSpec)
+        underTest.onMoved(listState.tiles[5].tile.tileSpec)
         assertThat(underTest.currentPosition()).isEqualTo(5)
 
         // Move the tile to the middle of the list
-        underTest.onMoved(listState.tiles[2].tileSpec)
+        underTest.onMoved(listState.tiles[2].tile.tileSpec)
         assertThat(underTest.currentPosition()).isEqualTo(2)
     }
 
@@ -69,13 +71,13 @@
         underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tileSpec)
+        underTest.onMoved(listState.tiles[5].tile.tileSpec)
 
         // Drop the tile
         underTest.onDrop()
 
         // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
     }
 
     @Test
@@ -87,19 +89,24 @@
         underTest.movedOutOfBounds()
 
         // Asserts the moving tile is not current
-        assertThat(listState.tiles.firstOrNull { it.tileSpec == TestEditTiles[0].tileSpec })
+        assertThat(
+                listState.tiles.firstOrNull { it.tile.tileSpec == TestEditTiles[0].tile.tileSpec }
+            )
             .isNull()
     }
 
     companion object {
-        private fun createEditTile(tileSpec: String): EditTileViewModel {
-            return EditTileViewModel(
-                tileSpec = TileSpec.create(tileSpec),
-                icon = Icon.Resource(0, null),
-                label = Text.Loaded("unused"),
-                appName = null,
-                isCurrent = true,
-                availableEditActions = emptySet(),
+        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon = Icon.Resource(0, null),
+                    label = Text.Loaded("unused"),
+                    appName = null,
+                    isCurrent = true,
+                    availableEditActions = emptySet(),
+                ),
+                1,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index e76d389..a3a6a33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -35,7 +37,7 @@
     @Test
     fun movingNonExistentTile_tileAdded() {
         val newTile = createEditTile("other_tile", false)
-        underTest.move(newTile, TestEditTiles[0].tileSpec)
+        underTest.move(newTile, TestEditTiles[0].tile.tileSpec)
 
         assertThat(underTest.tiles[0]).isEqualTo(newTile)
         assertThat(underTest.tiles.subList(1, underTest.tiles.size))
@@ -51,7 +53,7 @@
 
     @Test
     fun movingTileToItself_listUnchanged() {
-        underTest.move(TestEditTiles[0], TestEditTiles[0].tileSpec)
+        underTest.move(TestEditTiles[0], TestEditTiles[0].tile.tileSpec)
 
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
     }
@@ -59,7 +61,7 @@
     @Test
     fun movingTileToSameSection_listUpdates() {
         // Move tile at index 0 to index 1. Tile 0 should remain current.
-        underTest.move(TestEditTiles[0], TestEditTiles[1].tileSpec)
+        underTest.move(TestEditTiles[0], TestEditTiles[1].tile.tileSpec)
 
         // Assert the tiles 0 and 1 have changed places.
         assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
@@ -72,21 +74,27 @@
 
     fun removingTile_listUpdates() {
         // Remove tile at index 0
-        underTest.remove(TestEditTiles[0].tileSpec)
+        underTest.remove(TestEditTiles[0].tile.tileSpec)
 
         // Assert the tile was removed
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
     }
 
     companion object {
-        private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel {
-            return EditTileViewModel(
-                tileSpec = TileSpec.create(tileSpec),
-                icon = Icon.Resource(0, null),
-                label = Text.Loaded("unused"),
-                appName = null,
-                isCurrent = isCurrent,
-                availableEditActions = emptySet(),
+        private fun createEditTile(
+            tileSpec: String,
+            isCurrent: Boolean
+        ): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon = Icon.Resource(0, null),
+                    label = Text.Loaded("unused"),
+                    appName = null,
+                    isCurrent = isCurrent,
+                    availableEditActions = emptySet(),
+                ),
+                1,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
index 6df3f8d..0d93686 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -72,10 +72,10 @@
     }
 
     companion object {
-        fun extraLargeTile() = SizedTile(MockTileViewModel(TileSpec.create("XLarge")), 3)
+        fun extraLargeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("XLarge")), 3)
 
-        fun largeTile() = SizedTile(MockTileViewModel(TileSpec.create("large")), 2)
+        fun largeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("large")), 2)
 
-        fun smallTile() = SizedTile(MockTileViewModel(TileSpec.create("small")), 1)
+        fun smallTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("small")), 1)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index cfb84a7..d153e9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,7 +35,6 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AutoAddableSettingTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
index add33da..6a88664 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
@@ -43,7 +43,7 @@
     }
 
     @Test
-    fun transitionToOccluded() =
+    fun transitionToOccludedByOCCLUDEDTransition() =
         testScope.runTest {
             val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
             assertThat(isKeyguardOccluded).isFalse()
@@ -62,4 +62,25 @@
             )
             assertThat(isKeyguardOccluded).isFalse()
         }
+
+    @Test
+    fun transitionToOccludedByDREAMINGTransition() =
+        testScope.runTest {
+            val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
+            assertThat(isKeyguardOccluded).isFalse()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).isTrue()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 9fea7a2..2fb9e1e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -172,7 +172,7 @@
         }
 
     @Test
-    fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+    fun validatePaddingTopInSplitShade_usesLargeHeaderHelper() =
         testScope.runTest {
             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
             overrideResource(R.bool.config_use_split_notification_shade, true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 32f66c1..11504aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -172,7 +172,7 @@
     @Test
     fun shouldAskForZenDuration_changesWithSetting() =
         testScope.runTest {
-            val manualDnd = TestModeBuilder.MANUAL_DND
+            val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
 
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
             runCurrent()
@@ -201,7 +201,7 @@
     @Test
     fun activateMode_usesCorrectDuration() =
         testScope.runTest {
-            val manualDnd = TestModeBuilder.MANUAL_DND
+            val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
             zenModeRepository.addModes(listOf(manualDnd))
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 62161bf..bcad7e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -69,7 +69,7 @@
                         .setName("Disabled by other")
                         .setEnabled(false, /* byUser= */ false)
                         .build(),
-                    TestModeBuilder.MANUAL_DND,
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
                     TestModeBuilder()
                         .setName("Enabled")
                         .setEnabled(true)
@@ -91,7 +91,7 @@
                 assertThat(this.enabled).isEqualTo(false)
             }
             with(tiles?.elementAt(1)!!) {
-                assertThat(this.text).isEqualTo("Manual DND")
+                assertThat(this.text).isEqualTo("Do Not Disturb")
                 assertThat(this.subtext).isEqualTo("On")
                 assertThat(this.enabled).isEqualTo(true)
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
new file mode 100644
index 0000000..e281894
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.settings
+
+import android.database.ContentObserver
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/** Tests for [SettingsProxyExt]. */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class SettingsProxyExtTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    @Mock lateinit var settingsProxy: SettingsProxy
+    @Mock lateinit var userSettingsProxy: UserSettingsProxy
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_settingsProxy_registerContentObserverInvoked() =
+        testScope.runTest {
+            val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+            runCurrent()
+            verify(settingsProxy, times(2))
+                .registerContentObserver(any<String>(), any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_multipleSettings_SettingsProxy_registerContentObserverInvoked() =
+        testScope.runTest {
+            val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+            runCurrent()
+            verify(settingsProxy, times(2))
+                .registerContentObserverSync(any<String>(), any<ContentObserver>())
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(settingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(settingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+        testScope.runTest {
+            val unused by
+                collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+            runCurrent()
+            verify(userSettingsProxy, times(2))
+                .registerContentObserverForUser(any<String>(), any<ContentObserver>(), any<Int>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+        testScope.runTest {
+            val unused by
+                collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+            runCurrent()
+            verify(userSettingsProxy, times(2))
+                .registerContentObserverForUserSync(
+                    any<String>(),
+                    any<ContentObserver>(),
+                    any<Int>()
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagEnabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(
+                    userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+                    context = job
+                )
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(userSettingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+    fun observeFlow_bgFlagDisabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+        testScope.runTest {
+            val job = Job()
+            val unused by
+                collectLastValue(
+                    userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+                    context = job
+                )
+            runCurrent()
+            job.cancel()
+            runCurrent()
+            verify(userSettingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+        }
+
+    private companion object {
+        val SETTING_1 = "settings_1"
+        val SETTING_2 = "settings_2"
+    }
+}
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 8fa975b..e1b8ab4 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,7 +49,7 @@
             android:alpha="0.0"
             />
 
-        <!-- LINT.IfChange textColor -->
+        <!-- LINT.IfChange -->
         <TextView
             android:id="@+id/text"
             android:layout_width="0dp"
@@ -78,7 +78,7 @@
             android:layout_height="@dimen/chipbar_end_icon_size"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_warning"
-            android:tint="@color/GM2_red_600"
+            android:tint="@color/GM2_red_800"
             android:alpha="0.0"
             />
 
diff --git a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
index 6180bf5..9e84052 100644
--- a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
+++ b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
@@ -52,7 +52,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/attach_to_bugreport_switch"
@@ -80,7 +81,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"/>
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/winscope_switch"
@@ -108,7 +110,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/trace_debuggable_apps_switch"
@@ -136,7 +139,8 @@
             android:layout_weight="1"
             android:layout_gravity="fill_vertical"
             android:gravity="start"
-            android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no" />
 
         <Switch
             android:id="@+id/long_traces_switch"
diff --git a/packages/SystemUI/res/raw/trackpad_home_edu.json b/packages/SystemUI/res/raw/trackpad_home_edu.json
new file mode 100644
index 0000000..27db9fd
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":426,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Home_Dismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":2,"ty":3,"nm":"gesture:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277,197.321,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.78,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.527,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.884,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.789,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.62,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.476,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.353,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.209,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.039,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.212,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.896,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.197,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.536,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.4,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.939,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.375,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.791,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.751,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.459,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.006,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.442,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.798,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.092,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.339,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.546,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.721,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.87,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.995,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.191,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.378,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":197,"s":[100]},{"t":203,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":198,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":201,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.321],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.781],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.528],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.638],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.587],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.09],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.793],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.516],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.702],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,212.767],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,217.041],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,220.728],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,223.965],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,226.837],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,229.392],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,231.662],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,233.68],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,235.467],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,237.042],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,238.421],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.622],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.66],"t":214,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.55],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.299],"t":216,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.916],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.407],"t":218,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.572],"t":220,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.29],"t":221,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.866],"t":222,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.351],"t":223,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.782],"t":224,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.175],"t":225,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.597],"t":226,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.08],"t":227,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.638],"t":228,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.281],"t":229,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.017],"t":230,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.165],"t":238,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.365],"t":240,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.555],"t":242,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.785],"t":245,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.579],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.389],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.278],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,234.833],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,221.896],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,215.604],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,211.894],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,209.347],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.439],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,205.933],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,204.711],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,203.696],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.839],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.106],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.474],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.925],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.444],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.022],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.649],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.32],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.03],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.776],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.552],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.355],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.183],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.034],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.902],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.787],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.62],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":195,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":225,"s":[0,82.5,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[0,82.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[0,49.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":200,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":218,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":232,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":252,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Home_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":195,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":225,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"Home_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Home_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":195,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":201,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":231,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Home_Dismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_home_success.json b/packages/SystemUI/res/raw/trackpad_home_success.json
new file mode 100644
index 0000000..f14fde5
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadHome_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,140.5,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadHome_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Home_LofiApp","tt":1,"tp":4,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0350cd7..8cf0fb2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -160,8 +160,8 @@
 
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#EA4335</color>
-    <color name="GM2_red_600">#B3261E</color>
     <color name="GM2_red_700">#C5221F</color>
+    <color name="GM2_red_800">#B3261E</color>
 
     <color name="GM2_blue_300">#8AB4F8</color>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4bc4692..e49f8c8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3682,17 +3682,25 @@
     <string name="touchpad_tutorial_action_key_button">Action key</string>
     <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_done_button">Done</string>
-    <!-- Screen title after gesture was done successfully [CHAR LIMIT=NONE] -->
-    <string name="touchpad_tutorial_gesture_done">Great job!</string>
+    <!-- BACK GESTURE -->
     <!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_action_title">Go back</string>
     <!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
 Action + ESC for this.</string>
+    <!-- Screen title after gesture was done successfully [CHAR LIMIT=NONE] -->
+    <string name="touchpad_tutorial_gesture_done">Great job!</string>
     <!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
     <string name="touchpad_back_gesture_finished">You completed the go back gesture.</string>
-    <string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string>
-    <string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string>
+    <!-- HOME GESTURE -->
+    <!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_action_title">Go home</string>
+    <!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+    <!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_done">Nice!</string>
+    <!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
+    <string name="touchpad_home_gesture_finished">You completed the go home gesture.</string>
 
     <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
     <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 93c4630..d81a686 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
@@ -193,15 +194,18 @@
         private final Context mContext;
         private final MagnificationSettingsController.Callback mSettingsControllerCallback;
         private final SecureSettings mSecureSettings;
+        private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
         SettingsSupplier(Context context,
                 MagnificationSettingsController.Callback settingsControllerCallback,
                 DisplayManager displayManager,
-                SecureSettings secureSettings) {
+                SecureSettings secureSettings,
+                ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
             super(displayManager);
             mContext = context;
             mSettingsControllerCallback = settingsControllerCallback;
             mSecureSettings = secureSettings;
+            mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         }
 
         @Override
@@ -213,7 +217,8 @@
                     windowContext,
                     new SfVsyncFrameCallbackProvider(),
                     mSettingsControllerCallback,
-                    mSecureSettings);
+                    mSecureSettings,
+                    mViewCaptureAwareWindowManager);
         }
     }
 
@@ -227,10 +232,12 @@
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger,
-            IWindowManager iWindowManager, AccessibilityManager accessibilityManager) {
+            IWindowManager iWindowManager, AccessibilityManager accessibilityManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         this(context, mainHandler.getLooper(), executor, commandQueue,
                 modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
-                displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager);
+                displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -240,7 +247,8 @@
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger,
             IWindowManager iWindowManager,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mHandler = new Handler(looper) {
             @Override
             public void handleMessage(@NonNull Message msg) {
@@ -263,7 +271,8 @@
         mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
                 context, displayManager, mHandler, mExecutor, iWindowManager);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
-                mMagnificationSettingsControllerCallback, displayManager, secureSettings);
+                mMagnificationSettingsControllerCallback, displayManager, secureSettings,
+                viewCaptureAwareWindowManager);
 
         mModeSwitchesController.setClickListenerDelegate(
                 displayId -> mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index d9d9e37..e91bb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -46,6 +46,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ImageView;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.res.R;
@@ -76,6 +77,7 @@
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final ImageView mImageView;
     private final Runnable mWindowInsetChangeRunnable;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -99,17 +101,21 @@
         void onClick(int displayId);
     }
 
-    MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener) {
-        this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener);
+    MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
     MagnificationModeSwitch(Context context, @NonNull ImageView imageView,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener) {
+            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mClickListener = clickListener;
         mParams = createLayoutParams(context);
@@ -276,7 +282,7 @@
         mImageView.animate().cancel();
         mIsFadeOutAnimating = false;
         mImageView.setAlpha(0f);
-        mWindowManager.removeView(mImageView);
+        mViewCaptureAwareWindowManager.removeView(mImageView);
         mContext.unregisterComponentCallbacks(this);
         mIsVisible = false;
     }
@@ -310,7 +316,7 @@
                 mParams.y = mDraggableWindowBounds.bottom;
                 mToLeftScreenEdge = false;
             }
-            mWindowManager.addView(mImageView, mParams);
+            mViewCaptureAwareWindowManager.addView(mImageView, mParams);
             // Exclude magnification switch button from system gesture area.
             setSystemGestureExclusion();
             mIsVisible = true;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index caf5517..fc7535a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -26,6 +26,7 @@
 import android.util.Range;
 import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -60,8 +61,10 @@
             @UiContext Context context,
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
             @NonNull Callback settingsControllerCallback,
-            SecureSettings secureSettings) {
-        this(context, sfVsyncFrameProvider, settingsControllerCallback,  secureSettings, null);
+            SecureSettings secureSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        this(context, sfVsyncFrameProvider, settingsControllerCallback,  secureSettings, null,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -70,7 +73,8 @@
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
             @NonNull Callback settingsControllerCallback,
             SecureSettings secureSettings,
-            WindowMagnificationSettings windowMagnificationSettings) {
+            WindowMagnificationSettings windowMagnificationSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context.createWindowContext(
                 context.getDisplay(),
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
@@ -84,7 +88,7 @@
         } else {
             mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
                     mWindowMagnificationSettingsCallback,
-                    sfVsyncFrameProvider, secureSettings);
+                    sfVsyncFrameProvider, secureSettings, viewCaptureAwareWindowManager);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index 63f9cc2..53827e6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -25,6 +25,7 @@
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
 
@@ -47,8 +48,10 @@
     private ClickListener mClickListenerDelegate;
 
     @Inject
-    public ModeSwitchesController(Context context, DisplayManager displayManager) {
-        mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick);
+    public ModeSwitchesController(Context context, DisplayManager displayManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+        mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick,
+                viewCaptureAwareWindowManager);
     }
 
     @VisibleForTesting
@@ -115,6 +118,7 @@
 
         private final Context mContext;
         private final ClickListener mClickListener;
+        private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
         /**
          * Supplies the switch for the given display.
@@ -124,17 +128,20 @@
          * @param clickListener The callback that will run when the switch is clicked
          */
         SwitchSupplier(Context context, DisplayManager displayManager,
-                ClickListener clickListener) {
+                ClickListener clickListener,
+                ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
             super(displayManager);
             mContext = context;
             mClickListener = clickListener;
+            mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         }
 
         @Override
         protected MagnificationModeSwitch createInstance(Display display) {
             final Context uiContext = mContext.createWindowContext(display,
                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
-            return new MagnificationModeSwitch(uiContext, mClickListener);
+            return new MagnificationModeSwitch(uiContext, mClickListener,
+                    mViewCaptureAwareWindowManager);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 99d966d..9b6501e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -56,6 +56,7 @@
 import android.widget.SeekBar;
 import android.widget.Switch;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
@@ -75,6 +76,7 @@
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final SecureSettings mSecureSettings;
 
     private final Runnable mWindowInsetChangeRunnable;
@@ -135,10 +137,12 @@
 
     @VisibleForTesting
     WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings) {
+            SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mCallback = callback;
         mSecureSettings = secureSettings;
@@ -320,7 +324,7 @@
 
         // Unregister observer before removing view
         mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
-        mWindowManager.removeView(mSettingView);
+        mViewCaptureAwareWindowManager.removeView(mSettingView);
         mIsVisible = false;
         if (resetPosition) {
             mParams.x = 0;
@@ -378,7 +382,7 @@
                 mParams.y = mDraggableWindowBounds.bottom;
             }
 
-            mWindowManager.addView(mSettingView, mParams);
+            mViewCaptureAwareWindowManager.addView(mSettingView, mParams);
 
             mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 4dafa93..9d82e76 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -297,7 +297,7 @@
 
         val DeviceItem.isMediaDevice: Boolean
             get() =
-                cachedBluetoothDevice.connectableProfiles.any {
+                cachedBluetoothDevice.uiAccessibleProfiles.any {
                     it is A2dpProfile ||
                         it is HearingAidProfile ||
                         it is LeAudioProfile ||
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 03ef17b..2bcbc9a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.communal.widgets
 
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
 import android.content.Intent
+import android.content.IntentSender
 import android.os.Bundle
 import android.os.RemoteException
 import android.util.Log
@@ -66,12 +69,78 @@
         const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
     }
 
+    /**
+     * [ActivityController] handles closing the activity in the case it is backgrounded without
+     * waiting for an activity result
+     */
+    class ActivityController(activity: Activity) {
+        companion object {
+            private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
+        }
+
+        private var waitingForResult: Boolean = false
+
+        init {
+            activity.registerActivityLifecycleCallbacks(
+                object : ActivityLifecycleCallbacks {
+                    override fun onActivityCreated(
+                        activity: Activity,
+                        savedInstanceState: Bundle?
+                    ) {
+                        waitingForResult =
+                            savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
+                                ?: false
+                    }
+
+                    override fun onActivityStarted(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityResumed(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityPaused(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityStopped(activity: Activity) {
+                        // If we're not backgrounded due to waiting for a resul (either widget
+                        // selection
+                        // or configuration), finish activity.
+                        if (!waitingForResult) {
+                            activity.finish()
+                        }
+                    }
+
+                    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+                        outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult)
+                    }
+
+                    override fun onActivityDestroyed(activity: Activity) {
+                        // Nothing to implement.
+                    }
+                }
+            )
+        }
+
+        /**
+         * Invoked when waiting for an activity result changes, either initiating such wait or
+         * finishing due to the return of a result.
+         */
+        fun onWaitingForResult(waitingForResult: Boolean) {
+            this.waitingForResult = waitingForResult
+        }
+    }
+
     private val logger = Logger(logBuffer, "EditWidgetsActivity")
 
     private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
 
     private var shouldOpenWidgetPickerOnStart = false
 
+    private val activityController: ActivityController = ActivityController(this)
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -154,6 +223,13 @@
                 // edit mode
                 communalViewModel.currentScene.first { it == CommunalScenes.Blank }
                 communalViewModel.setEditModeState(EditModeState.SHOWING)
+
+                // Show the widget picker, if necessary, after the edit activity has animated in.
+                // Waiting until after the activity has appeared avoids transitions issues.
+                if (shouldOpenWidgetPickerOnStart) {
+                    onOpenWidgetPicker()
+                    shouldOpenWidgetPickerOnStart = false
+                }
             }
         }
     }
@@ -186,7 +262,34 @@
         }
     }
 
+    override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
+        activityController.onWaitingForResult(true)
+        super.startActivityForResult(intent, requestCode, options)
+    }
+
+    override fun startIntentSenderForResult(
+        intent: IntentSender,
+        requestCode: Int,
+        fillInIntent: Intent?,
+        flagsMask: Int,
+        flagsValues: Int,
+        extraFlags: Int,
+        options: Bundle?
+    ) {
+        activityController.onWaitingForResult(true)
+        super.startIntentSenderForResult(
+            intent,
+            requestCode,
+            fillInIntent,
+            flagsMask,
+            flagsValues,
+            extraFlags,
+            options
+        )
+    }
+
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        activityController.onWaitingForResult(false)
         super.onActivityResult(requestCode, resultCode, data)
         if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
             widgetConfigurator.setConfigurationResult(resultCode)
@@ -198,11 +301,6 @@
 
         communalViewModel.setEditActivityShowing(true)
 
-        if (shouldOpenWidgetPickerOnStart) {
-            onOpenWidgetPicker()
-            shouldOpenWidgetPickerOnStart = false
-        }
-
         logger.i("Starting the communal widget editor activity")
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 4286646..b0f2c18 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.dreams.DreamMonitor
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
 import com.android.systemui.globalactions.GlobalActionsComponent
-import com.android.systemui.inputdevice.oobe.KeyboardTouchpadOobeTutorialCoreStartable
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialCoreStartable
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
 import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -258,9 +258,9 @@
 
     @Binds
     @IntoMap
-    @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
-    abstract fun bindOobeSchedulerCoreStartable(
-        listener: KeyboardTouchpadOobeTutorialCoreStartable
+    @ClassKey(KeyboardTouchpadTutorialCoreStartable::class)
+    abstract fun bindKeyboardTouchpadTutorialCoreStartable(
+        listener: KeyboardTouchpadTutorialCoreStartable
     ): CoreStartable
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 69ddb62..40e2f17 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -160,7 +160,10 @@
                     .stateIn(
                         bgApplicationScope,
                         SharingStarted.WhileSubscribed(),
-                        emptySet(),
+                        // This is necessary because there might be multiple displays, and we could
+                        // have missed events for those added before this process or flow started.
+                        // Note it causes a binder call from the main thread (it's traced).
+                        getDisplays().map { display -> display.displayId }.toSet(),
                     )
             } else {
                 oldEnabledDisplays.map { enabledDisplaysSet ->
@@ -186,8 +189,12 @@
                 .stateIn(
                     bgApplicationScope,
                     started = SharingStarted.WhileSubscribed(),
-                    initialValue = setOf(defaultDisplay)
-                )
+                    // This triggers a single binder call on the UI thread per process. The
+                    // alternative would be to use sharedFlows, but they are prohibited due to
+                    // performance concerns.
+                    // Ultimately, this is a trade-off between a one-time UI thread binder call and
+                    // the constant overhead of sharedFlows.
+                    initialValue = getDisplays())
         } else {
             oldEnabledDisplays
         }
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 532b123..096556f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -18,10 +18,10 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.education.data.repository.ContextualEducationRepository
-import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import com.android.systemui.education.data.repository.UserContextualEducationRepository
 import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
@@ -42,7 +42,7 @@
 interface ContextualEducationModule {
     @Binds
     fun bindContextualEducationRepository(
-        impl: ContextualEducationRepositoryImpl
+        impl: UserContextualEducationRepository
     ): ContextualEducationRepository
 
     @Qualifier annotation class EduDataStoreScope
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 9f6cb4d..a171f87 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -26,4 +26,6 @@
     val signalCount: Int = 0,
     val educationShownCount: Int = 0,
     val lastShortcutTriggeredTime: Instant? = null,
+    val usageSessionStartTime: Instant? = null,
+    val lastEducationTime: Instant? = null,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
deleted file mode 100644
index 52ccba4..0000000
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.education.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
-import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Encapsulates the functions of ContextualEducationRepository. */
-interface ContextualEducationRepository {
-    fun setUser(userId: Int)
-
-    fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
-
-    suspend fun incrementSignalCount(gestureType: GestureType)
-
-    suspend fun updateShortcutTriggerTime(gestureType: GestureType)
-}
-
-/**
- * Provide methods to read and update on field level and allow setting datastore when user is
- * changed
- */
-@SysUISingleton
-class ContextualEducationRepositoryImpl
-@Inject
-constructor(
-    @EduClock private val clock: Clock,
-    private val userEduRepository: UserContextualEducationRepository
-) : ContextualEducationRepository {
-    /** To change data store when user is changed */
-    override fun setUser(userId: Int) = userEduRepository.setUser(userId)
-
-    override fun readGestureEduModelFlow(gestureType: GestureType) =
-        userEduRepository.readGestureEduModelFlow(gestureType)
-
-    override suspend fun incrementSignalCount(gestureType: GestureType) {
-        userEduRepository.updateGestureEduModel(gestureType) {
-            it.copy(signalCount = it.signalCount + 1)
-        }
-    }
-
-    override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
-        userEduRepository.updateGestureEduModel(gestureType) {
-            it.copy(lastShortcutTriggeredTime = clock.instant())
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 4b37b29..7c3d6338 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -25,9 +25,9 @@
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.core.longPreferencesKey
 import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
 import com.android.systemui.education.data.model.GestureEduModel
 import java.time.Instant
@@ -43,10 +43,24 @@
 import kotlinx.coroutines.flow.map
 
 /**
- * A contextual education repository to:
- * 1) store education data per user
- * 2) provide methods to read and update data on model-level
- * 3) provide method to enable changing datastore when user is changed
+ * Allows to:
+ * 1) read and update data on model-level
+ * 2) change data store when user is changed
+ */
+interface ContextualEducationRepository {
+    fun setUser(userId: Int)
+
+    fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+    suspend fun updateGestureEduModel(
+        gestureType: GestureType,
+        transform: (GestureEduModel) -> GestureEduModel
+    )
+}
+
+/**
+ * Implementation of [ContextualEducationRepository] that uses [androidx.datastore.preferences.core]
+ * for storage. Data is stored per user.
  */
 @SysUISingleton
 class UserContextualEducationRepository
@@ -54,11 +68,13 @@
 constructor(
     @Application private val applicationContext: Context,
     @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
-) {
+) : ContextualEducationRepository {
     companion object {
         const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
         const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
         const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
+        const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
+        const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
 
         const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
     }
@@ -70,7 +86,7 @@
     @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
     private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
 
-    internal fun setUser(userId: Int) {
+    override fun setUser(userId: Int) {
         dataStoreScope?.cancel()
         val newDsScope = dataStoreScopeProvider.get()
         datastore.value =
@@ -85,7 +101,7 @@
         dataStoreScope = newDsScope
     }
 
-    internal fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
+    override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
         prefData.map { preferences -> getGestureEduModel(gestureType, preferences) }
 
     private fun getGestureEduModel(
@@ -97,12 +113,20 @@
             educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
             lastShortcutTriggeredTime =
                 preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
-                    Instant.ofEpochMilli(it)
+                    Instant.ofEpochSecond(it)
+                },
+            usageSessionStartTime =
+                preferences[getUsageSessionStartTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
+                },
+            lastEducationTime =
+                preferences[getLastEducationTimeKey(gestureType)]?.let {
+                    Instant.ofEpochSecond(it)
                 },
         )
     }
 
-    internal suspend fun updateGestureEduModel(
+    override suspend fun updateGestureEduModel(
         gestureType: GestureType,
         transform: (GestureEduModel) -> GestureEduModel
     ) {
@@ -111,11 +135,21 @@
             val updatedModel = transform(currentModel)
             preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
             preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
-            updateTimeByInstant(
+            setInstant(
                 preferences,
                 updatedModel.lastShortcutTriggeredTime,
                 getLastShortcutTriggeredTimeKey(gestureType)
             )
+            setInstant(
+                preferences,
+                updatedModel.usageSessionStartTime,
+                getUsageSessionStartTimeKey(gestureType)
+            )
+            setInstant(
+                preferences,
+                updatedModel.lastEducationTime,
+                getLastEducationTimeKey(gestureType)
+            )
         }
     }
 
@@ -128,13 +162,22 @@
     private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
         longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
 
-    private fun updateTimeByInstant(
+    private fun getUsageSessionStartTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+        longPreferencesKey(gestureType.name + USAGE_SESSION_START_TIME_SUFFIX)
+
+    private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+        longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+
+    private fun setInstant(
         preferences: MutablePreferences,
         instant: Instant?,
         key: Preferences.Key<Long>
     ) {
         if (instant != null) {
-            preferences[key] = instant.toEpochMilli()
+            // Use epochSecond because an instant is defined as a signed long (64bit number) of
+            // seconds. Using toEpochMilli() on Instant.MIN or Instant.MAX will throw exception
+            // when converting to a long. So we use second instead of milliseconds for storage.
+            preferences[key] = instant.epochSecond
         } else {
             preferences.remove(key)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index bee289d..db5c386 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -17,13 +17,15 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
 import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import java.time.Clock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -43,6 +45,7 @@
 constructor(
     @Background private val backgroundScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @EduClock private val clock: Clock,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val repository: ContextualEducationRepository,
 ) : CoreStartable {
@@ -64,9 +67,37 @@
             .flowOn(backgroundDispatcher)
     }
 
-    suspend fun incrementSignalCount(gestureType: GestureType) =
-        repository.incrementSignalCount(gestureType)
+    suspend fun incrementSignalCount(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(
+                signalCount = it.signalCount + 1,
+                usageSessionStartTime =
+                    if (it.signalCount == 0) clock.instant() else it.usageSessionStartTime
+            )
+        }
+    }
 
-    suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
-        repository.updateShortcutTriggerTime(gestureType)
+    suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(lastShortcutTriggeredTime = clock.instant())
+        }
+    }
+
+    suspend fun updateOnEduTriggered(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(
+                // Reset signal counter and usageSessionStartTime after edu triggered
+                signalCount = 0,
+                lastEducationTime = clock.instant(),
+                educationShownCount = it.educationShownCount + 1,
+                usageSessionStartTime = null
+            )
+        }
+    }
+
+    suspend fun startNewUsageSession(gestureType: GestureType) {
+        repository.updateGestureEduModel(gestureType) {
+            it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 9016c73..3a3fb8c 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -17,17 +17,19 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType.BACK
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
 import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.shared.model.EducationInfo
 import com.android.systemui.education.shared.model.EducationUiType
+import java.time.Clock
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.hours
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
 
 /** Allow listening to new contextual education triggered */
@@ -36,11 +38,13 @@
 @Inject
 constructor(
     @Background private val backgroundScope: CoroutineScope,
-    private val contextualEducationInteractor: ContextualEducationInteractor
+    private val contextualEducationInteractor: ContextualEducationInteractor,
+    @EduClock private val clock: Clock,
 ) : CoreStartable {
 
     companion object {
         const val MAX_SIGNAL_COUNT: Int = 2
+        val usageSessionDuration = 72.hours
     }
 
     private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
@@ -48,25 +52,30 @@
 
     override fun start() {
         backgroundScope.launch {
-            contextualEducationInteractor.backGestureModelFlow
-                .mapNotNull { getEduType(it) }
-                .collect { _educationTriggered.value = EducationInfo(BACK, it) }
-        }
-    }
-
-    private fun getEduType(model: GestureEduModel): EducationUiType? {
-        if (isEducationNeeded(model)) {
-            return EducationUiType.Toast
-        } else {
-            return null
+            contextualEducationInteractor.backGestureModelFlow.collect {
+                if (isUsageSessionExpired(it)) {
+                    contextualEducationInteractor.startNewUsageSession(BACK)
+                } else if (isEducationNeeded(it)) {
+                    _educationTriggered.value = EducationInfo(BACK, getEduType(it))
+                    contextualEducationInteractor.updateOnEduTriggered(BACK)
+                }
+            }
         }
     }
 
     private fun isEducationNeeded(model: GestureEduModel): Boolean {
         // Todo: b/354884305 - add complete education logic to show education in correct scenarios
-        val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+        val noShortcutTriggered = model.lastShortcutTriggeredTime == null
         val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
-
-        return shortcutWasTriggered && signalCountReached
+        return noShortcutTriggered && signalCountReached
     }
+
+    private fun isUsageSessionExpired(model: GestureEduModel): Boolean {
+        return model.usageSessionStartTime
+            ?.plusSeconds(usageSessionDuration.inWholeSeconds)
+            ?.isBefore(clock.instant()) ?: false
+    }
+
+    private fun getEduType(model: GestureEduModel) =
+        if (model.educationShownCount > 0) EducationUiType.Notification else EducationUiType.Toast
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 1d1ac5a..562ba36 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -34,8 +34,6 @@
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.qs.flags.NewQsUI
-import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
@@ -76,9 +74,6 @@
         // DualShade dependencies
         DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag()
 
-        // QS Fragment using Compose dependencies
-        QSComposeFragment.token dependsOn NewQsUI.token
-
         // Status bar chip dependencies
         statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken
         statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
deleted file mode 100644
index b014c08..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
+++ /dev/null
@@ -1,97 +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.inputdevice.oobe.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.inputdevice.oobe.data.model.DeviceSchedulerInfo
-import com.android.systemui.inputdevice.oobe.data.model.OobeSchedulerInfo
-import com.android.systemui.keyboard.data.repository.KeyboardRepository
-import com.android.systemui.touchpad.data.repository.TouchpadRepository
-import java.time.Duration
-import java.time.Instant
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-/**
- * When the first time a keyboard or touchpad id connected, wait for [LAUNCH_DELAY], then launch the
- * tutorial as soon as there's a connected device
- */
-@SysUISingleton
-class OobeSchedulerInteractor
-@Inject
-constructor(
-    @Application private val context: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val keyboardRepository: KeyboardRepository,
-    private val touchpadRepository: TouchpadRepository
-) {
-    private val info = OobeSchedulerInfo()
-
-    fun start() {
-        if (!info.keyboard.isLaunched) {
-            applicationScope.launch {
-                schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
-            }
-        }
-        if (!info.touchpad.isLaunched) {
-            applicationScope.launch {
-                schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
-            }
-        }
-    }
-
-    private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
-        if (!info.wasEverConnected) {
-            waitForDeviceConnection(isAnyDeviceConnected)
-            info.connectionTime = Instant.now().toEpochMilli()
-        }
-        delay(remainingTimeMillis(info.connectionTime!!))
-        waitForDeviceConnection(isAnyDeviceConnected)
-        info.isLaunched = true
-        launchOobe()
-    }
-
-    private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
-        return isAnyDeviceConnected.filter { it }.first()
-    }
-
-    private fun launchOobe() {
-        val intent = Intent(TUTORIAL_ACTION)
-        intent.addCategory(Intent.CATEGORY_DEFAULT)
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        context.startActivity(intent)
-    }
-
-    private fun remainingTimeMillis(start: Long): Long {
-        val elapsed = Instant.now().toEpochMilli() - start
-        return LAUNCH_DELAY - elapsed
-    }
-
-    companion object {
-        const val TAG = "OobeSchedulerInteractor"
-        const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
-        private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 701d3da..e8e1dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -14,23 +14,24 @@
  * limitations under the License.
  */
 
-package com.android.systemui.inputdevice.oobe
+package com.android.systemui.inputdevice.tutorial
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.oobe.domain.interactor.OobeSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
 import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
 import dagger.Lazy
 import javax.inject.Inject
 
-/** A [CoreStartable] to launch a scheduler for keyboard and touchpad OOBE education */
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
 @SysUISingleton
-class KeyboardTouchpadOobeTutorialCoreStartable
+class KeyboardTouchpadTutorialCoreStartable
 @Inject
-constructor(private val oobeSchedulerInteractor: Lazy<OobeSchedulerInteractor>) : CoreStartable {
+constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
+    CoreStartable {
     override fun start() {
         if (newTouchpadGesturesTutorial()) {
-            oobeSchedulerInteractor.get().start()
+            tutorialSchedulerInteractor.get().start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index e5aedc0..cfe64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.inputdevice.oobe.data.model
+package com.android.systemui.inputdevice.tutorial.data.model
 
-data class OobeSchedulerInfo(
+data class TutorialSchedulerInfo(
     val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
     val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
 )
 
-data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
+data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) {
     val wasEverConnected: Boolean
-        get() = connectionTime != null
+        get() = connectTime != null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
new file mode 100644
index 0000000..31ff018
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.longPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+import com.android.systemui.inputdevice.tutorial.data.model.TutorialSchedulerInfo
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class TutorialSchedulerRepository
+@Inject
+constructor(@Application private val applicationContext: Context) {
+
+    private val Context.dataStore: DataStore<Preferences> by
+        preferencesDataStore(name = DATASTORE_NAME)
+
+    suspend fun loadData(): TutorialSchedulerInfo {
+        return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first()
+    }
+
+    suspend fun updateConnectTime(device: DeviceType, time: Long) {
+        applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time }
+    }
+
+    suspend fun updateLaunch(device: DeviceType) {
+        applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
+    }
+
+    private fun getSchedulerInfo(pref: Preferences): TutorialSchedulerInfo {
+        return TutorialSchedulerInfo(
+            keyboard = getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
+            touchpad = getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
+        )
+    }
+
+    private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
+        val isLaunched = pref[getLaunchedKey(device)] ?: false
+        val connectionTime = pref[getConnectKey(device)] ?: null
+        return DeviceSchedulerInfo(isLaunched, connectionTime)
+    }
+
+    private fun getLaunchedKey(device: DeviceType) =
+        booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX)
+
+    private fun getConnectKey(device: DeviceType) =
+        longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
+
+    companion object {
+        const val DATASTORE_NAME = "TutorialScheduler"
+        const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
+        const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
+    }
+}
+
+enum class DeviceType {
+    KEYBOARD,
+    TOUCHPAD
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
new file mode 100644
index 0000000..05e1044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.time.Duration
+import java.time.Instant
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
+ * tutorial as soon as there's a connected device
+ */
+@SysUISingleton
+class TutorialSchedulerInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Application private val applicationScope: CoroutineScope,
+    private val keyboardRepository: KeyboardRepository,
+    private val touchpadRepository: TouchpadRepository,
+    private val tutorialSchedulerRepository: TutorialSchedulerRepository
+) {
+    fun start() {
+        applicationScope.launch {
+            val info = tutorialSchedulerRepository.loadData()
+            if (!info.keyboard.isLaunched) {
+                applicationScope.launch {
+                    schedule(
+                        keyboardRepository.isAnyKeyboardConnected,
+                        info.keyboard,
+                        DeviceType.KEYBOARD
+                    )
+                }
+            }
+            if (!info.touchpad.isLaunched) {
+                applicationScope.launch {
+                    schedule(
+                        touchpadRepository.isAnyTouchpadConnected,
+                        info.touchpad,
+                        DeviceType.TOUCHPAD
+                    )
+                }
+            }
+        }
+    }
+
+    private suspend fun schedule(
+        isAnyDeviceConnected: Flow<Boolean>,
+        info: DeviceSchedulerInfo,
+        deviceType: DeviceType
+    ) {
+        if (!info.wasEverConnected) {
+            waitForDeviceConnection(isAnyDeviceConnected)
+            info.connectTime = Instant.now().toEpochMilli()
+            tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!)
+        }
+        delay(remainingTimeMillis(info.connectTime!!))
+        waitForDeviceConnection(isAnyDeviceConnected)
+        info.isLaunched = true
+        tutorialSchedulerRepository.updateLaunch(deviceType)
+        launchTutorial()
+    }
+
+    private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
+        return isAnyDeviceConnected.filter { it }.first()
+    }
+
+    private fun launchTutorial() {
+        val intent = Intent(TUTORIAL_ACTION)
+        intent.addCategory(Intent.CATEGORY_DEFAULT)
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        context.startActivity(intent)
+    }
+
+    private fun remainingTimeMillis(start: Long): Long {
+        val elapsed = Instant.now().toEpochMilli() - start
+        return LAUNCH_DELAY - elapsed
+    }
+
+    companion object {
+        const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+        private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 380e361..6ac33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -24,7 +24,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
@@ -64,13 +63,7 @@
                 val useLargeScreenHeader =
                     context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
                 val marginTopLargeScreen =
-                    if (centralizedStatusBarHeightFix()) {
-                        largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                    } else {
-                        context.resources.getDimensionPixelSize(
-                            R.dimen.large_screen_shade_header_height
-                        )
-                    }
+                    largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
                 connect(
                     R.id.nssl_placeholder,
                     TOP,
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 661da6d..c2b5d98 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -227,13 +227,33 @@
 }
 
 /**
+ * Runs the given [block] in a new coroutine when `this` [View]'s Window's [WindowLifecycleState] is
+ * at least at [state] (or immediately after calling this function if the window is already at least
+ * at [state]), automatically canceling the work when the window is no longer at least at that
+ * state.
+ *
+ * [block] may be run multiple times, running once per every time this` [View]'s Window's
+ * [WindowLifecycleState] becomes at least at [state].
+ */
+suspend fun View.repeatOnWindowLifecycle(
+    state: WindowLifecycleState,
+    block: suspend CoroutineScope.() -> Unit,
+): Nothing {
+    when (state) {
+        WindowLifecycleState.ATTACHED -> repeatWhenAttachedToWindow(block)
+        WindowLifecycleState.VISIBLE -> repeatWhenWindowIsVisible(block)
+        WindowLifecycleState.FOCUSED -> repeatWhenWindowHasFocus(block)
+    }
+}
+
+/**
  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
  * function, if the view was already attached), automatically canceling the work when the view
  * becomes detached.
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the view is attached.
+ * [block] may be run multiple times, running once per every time the view is attached.
  */
 @MainThread
 suspend fun View.repeatWhenAttachedToWindow(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -249,7 +269,7 @@
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the window becomes visible.
+ * [block] may be run multiple times, running once per every time the window becomes visible.
  */
 @MainThread
 suspend fun View.repeatWhenWindowIsVisible(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -265,7 +285,7 @@
  *
  * Only use from the main thread.
  *
- * The [block] may be run multiple times, running once per every time the window is focused.
+ * [block] may be run multiple times, running once per every time the window is focused.
  */
 @MainThread
 suspend fun View.repeatWhenWindowHasFocus(block: suspend CoroutineScope.() -> Unit): Nothing {
@@ -274,6 +294,21 @@
     awaitCancellation() // satisfies return type of Nothing
 }
 
+/** Lifecycle states for a [View]'s interaction with a [android.view.Window]. */
+enum class WindowLifecycleState {
+    /** Indicates that the [View] is attached to a [android.view.Window]. */
+    ATTACHED,
+    /**
+     * Indicates that the [View] is attached to a [android.view.Window], and the window is visible.
+     */
+    VISIBLE,
+    /**
+     * Indicates that the [View] is attached to a [android.view.Window], and the window is visible
+     * and focused.
+     */
+    FOCUSED
+}
+
 private val View.isAttached
     get() = conflatedCallbackFlow {
         val onAttachListener =
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 0af5fea..7731481 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.lifecycle
 
+import android.view.View
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Base class for all System UI view-models. */
 abstract class SysUiViewModel : SafeActivatable() {
@@ -37,8 +38,20 @@
 fun <T : SysUiViewModel> rememberViewModel(
     key: Any = Unit,
     factory: () -> T,
-): T {
-    val instance = remember(key) { factory() }
-    LaunchedEffect(instance) { instance.activate() }
-    return instance
-}
+): T = rememberActivated(key, factory)
+
+/**
+ * Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated
+ * whenever `this` [View]'s Window's [WindowLifecycleState] is at least at
+ * [minWindowLifecycleState], and is automatically canceled once that is no longer the case.
+ */
+suspend fun <T : SysUiViewModel> View.viewModel(
+    minWindowLifecycleState: WindowLifecycleState,
+    factory: () -> T,
+    block: suspend CoroutineScope.(T) -> Unit,
+): Nothing =
+    repeatOnWindowLifecycle(minWindowLifecycleState) {
+        val instance = factory()
+        launch { instance.activate() }
+        block(instance)
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index db0676e..9939075 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -18,8 +18,6 @@
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
@@ -194,12 +192,7 @@
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
         int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
         if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
-            topPadding =
-                    centralizedStatusBarHeightFix()
-                            ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
-                            : mContext.getResources()
-                                    .getDimensionPixelSize(
-                                            R.dimen.large_screen_shade_header_height);
+            topPadding = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
         }
         if (mQSPanelContainer != null) {
             mQSPanelContainer.setPaddingRelative(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 9c88eb9..5a3f1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,8 +17,6 @@
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -100,10 +98,7 @@
             qqsLP.topMargin = mContext.getResources()
                     .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
         } else {
-            qqsLP.topMargin = centralizedStatusBarHeightFix()
-                    ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
-                    : mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+            qqsLP.topMargin = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
         }
         mHeaderQsPanel.setLayoutParams(qqsLP);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
index 664d496..8772c51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.qsUiRefactorComposeFragment() && NewQsUI.isEnabled
+        get() = Flags.qsUiRefactorComposeFragment()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index b18358c..6dcdea9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -40,15 +40,15 @@
     @Application private val applicationScope: CoroutineScope
 ) {
 
-    private val largeTilesSpecs =
+    val largeTilesSpecs =
         preferencesInteractor.largeTilesSpecs
             .onEach { logChange(it) }
             .stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
 
     fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
 
-    fun resize(spec: TileSpec, toIcon: Boolean) {
-        if (toIcon) {
+    fun resize(spec: TileSpec) {
+        if (largeTilesSpecs.value.contains(spec)) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
         } else {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 0fe79af..874b3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -19,6 +19,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.shared.model.TileRow
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -38,17 +39,12 @@
     override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
         val newTiles: MutableList<TileSpec> = mutableListOf()
         val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
-        val tilesQueue =
+        val tilesQueue: ArrayDeque<SizedTile<TileSpec>> =
             ArrayDeque(
                 tiles.map {
-                    SizedTile(
+                    SizedTileImpl(
                         it,
-                        width =
-                            if (iconTilesInteractor.isIconTile(it)) {
-                                1
-                            } else {
-                                2
-                            }
+                        if (iconTilesInteractor.isIconTile(it)) 1 else 2,
                     )
                 }
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
index 7e4381b..17b73a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -17,7 +17,17 @@
 package com.android.systemui.qs.panels.shared.model
 
 /** Represents a tile of type [T] associated with a width */
-data class SizedTile<T>(val tile: T, val width: Int)
+interface SizedTile<T> {
+    val tile: T
+    val width: Int
+    val isIcon: Boolean
+        get() = width == 1
+}
+
+data class SizedTileImpl<T>(
+    override val tile: T,
+    override val width: Int,
+) : SizedTile<T>
 
 /** Represents a row of [SizedTile] with a maximum width of [columns] */
 class TileRow<T>(private val columns: Int) {
@@ -51,3 +61,26 @@
 
     fun isFull(): Boolean = availableColumns == 0
 }
+
+/**
+ * Converts a list of [SizedTile] to a sequence of rows based on the number of columns of the grid
+ */
+fun <T> splitInRowsSequence(
+    tiles: List<SizedTile<T>>,
+    columns: Int,
+): Sequence<List<SizedTile<T>>> = sequence {
+    val row = TileRow<T>(columns)
+    for (tile in tiles) {
+        check(tile.width <= columns)
+        if (!row.maybeAddTile(tile)) {
+            // Couldn't add tile to previous row, create a row with the current tiles
+            // and start a new one
+            yield(row.tiles)
+            row.clear()
+            row.maybeAddTile(tile)
+        }
+    }
+    if (row.tiles.isNotEmpty()) {
+        yield(row.tiles)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 71deeb6..2c57813 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -29,13 +29,14 @@
 import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.draganddrop.mimeTypes
+import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 @Composable
 fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
-    val sourceSpec: MutableState<EditTileViewModel?> = remember { mutableStateOf(null) }
-    return remember(listState) { DragAndDropState(sourceSpec, listState) }
+    val draggedCell: MutableState<SizedTile<EditTileViewModel>?> = remember { mutableStateOf(null) }
+    return remember(listState) { DragAndDropState(draggedCell, listState) }
 }
 
 /**
@@ -43,37 +44,37 @@
  * drop events.
  */
 class DragAndDropState(
-    val sourceSpec: MutableState<EditTileViewModel?>,
-    private val listState: EditTileListState
+    val draggedCell: MutableState<SizedTile<EditTileViewModel>?>,
+    private val listState: EditTileListState,
 ) {
     val dragInProgress: Boolean
-        get() = sourceSpec.value != null
+        get() = draggedCell.value != null
 
     /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
     fun currentPosition(): Int {
-        return sourceSpec.value?.let { listState.indexOf(it.tileSpec) } ?: -1
+        return draggedCell.value?.let { listState.indexOf(it.tile.tileSpec) } ?: -1
     }
 
     fun isMoving(tileSpec: TileSpec): Boolean {
-        return sourceSpec.value?.let { it.tileSpec == tileSpec } ?: false
+        return draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
     }
 
-    fun onStarted(tile: EditTileViewModel) {
-        sourceSpec.value = tile
+    fun onStarted(cell: SizedTile<EditTileViewModel>) {
+        draggedCell.value = cell
     }
 
     fun onMoved(targetSpec: TileSpec) {
-        sourceSpec.value?.let { listState.move(it, targetSpec) }
+        draggedCell.value?.let { listState.move(it, targetSpec) }
     }
 
     fun movedOutOfBounds() {
         // Removing the tiles from the current tile grid if it moves out of bounds. This clears
         // the spacer and makes it apparent that dropping the tile at that point would remove it.
-        sourceSpec.value?.let { listState.remove(it.tileSpec) }
+        draggedCell.value?.let { listState.remove(it.tile.tileSpec) }
     }
 
     fun onDrop() {
-        sourceSpec.value = null
+        draggedCell.value = null
     }
 }
 
@@ -97,8 +98,8 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -112,7 +113,7 @@
     return dragAndDropTarget(
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
         },
         target = target,
     )
@@ -134,8 +135,8 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec)
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec)
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -176,8 +177,8 @@
                 }
 
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell.value?.let {
+                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -188,23 +189,23 @@
         target = target,
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
         },
     )
 }
 
 fun Modifier.dragAndDropTileSource(
-    tile: EditTileViewModel,
+    sizedTile: SizedTile<EditTileViewModel>,
     onTap: (TileSpec) -> Unit,
     onDoubleTap: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState
 ): Modifier {
     return dragAndDropSource {
         detectTapGestures(
-            onTap = { onTap(tile.tileSpec) },
-            onDoubleTap = { onDoubleTap(tile.tileSpec) },
+            onTap = { onTap(sizedTile.tile.tileSpec) },
+            onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
             onLongPress = {
-                dragAndDropState.onStarted(tile)
+                dragAndDropState.onStarted(sizedTile)
 
                 // The tilespec from the ClipData transferred isn't actually needed as we're moving
                 // a tile within the same application. We're using a custom MIME type to limit the
@@ -214,7 +215,7 @@
                         ClipData(
                             QsDragAndDrop.CLIPDATA_LABEL,
                             arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
-                            ClipData.Item(tile.tileSpec.spec)
+                            ClipData.Item(sizedTile.tile.tileSpec.spec)
                         )
                     )
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index e0fed28..fa3008e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -20,22 +20,23 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.runtime.toMutableStateList
+import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 @Composable
 fun rememberEditListState(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
 ): EditTileListState {
     return remember(tiles) { EditTileListState(tiles) }
 }
 
 /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<EditTileViewModel>) {
-    val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()
+class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>) {
+    val tiles: SnapshotStateList<SizedTile<EditTileViewModel>> = tiles.toMutableStateList()
 
-    fun move(tile: EditTileViewModel, target: TileSpec) {
-        val fromIndex = indexOf(tile.tileSpec)
+    fun move(sizedTile: SizedTile<EditTileViewModel>, target: TileSpec) {
+        val fromIndex = indexOf(sizedTile.tile.tileSpec)
         val toIndex = indexOf(target)
 
         if (toIndex == -1 || fromIndex == toIndex) {
@@ -44,7 +45,7 @@
 
         if (fromIndex == -1) {
             // If tile isn't in the list, simply insert it
-            tiles.add(toIndex, tile)
+            tiles.add(toIndex, sizedTile)
         } else {
             // If tile is present in the list, move it
             tiles.apply { add(toIndex, removeAt(fromIndex)) }
@@ -52,10 +53,10 @@
     }
 
     fun remove(tileSpec: TileSpec) {
-        tiles.removeIf { it.tileSpec == tileSpec }
+        tiles.removeIf { it.tile.tileSpec == tileSpec }
     }
 
     fun indexOf(tileSpec: TileSpec): Int {
-        return tiles.indexOfFirst { it.tileSpec == tileSpec }
+        return tiles.indexOfFirst { it.tile.tileSpec == tileSpec }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index add830e..bd925fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -22,12 +22,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
@@ -56,13 +56,14 @@
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
-            items(tiles.size, span = { index -> GridItemSpan(tiles[index].spec.width()) }) { index
-                ->
+            items(sizedTiles.size, span = { index -> GridItemSpan(sizedTiles[index].width) }) {
+                index ->
                 Tile(
-                    tile = tiles[index],
-                    iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec),
+                    tile = sizedTiles[index].tile,
+                    iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
                     modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
@@ -77,13 +78,21 @@
         onRemoveTile: (TileSpec) -> Unit,
     ) {
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
-        val isIcon: (TileSpec) -> Boolean by rememberUpdatedState { tileSpec ->
-            iconTilesViewModel.isIconTile(tileSpec)
-        }
+        val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
+
+        // Non-current tiles should always be displayed as icon tiles.
+        val sizedTiles =
+            remember(tiles, largeTiles) {
+                tiles.map {
+                    SizedTileImpl(
+                        it,
+                        if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 else 2,
+                    )
+                }
+            }
 
         DefaultEditTileGrid(
-            tiles = tiles,
-            isIconOnly = isIcon,
+            sizedTiles = sizedTiles,
             columns = columns,
             modifier = modifier,
             onAddTile = onAddTile,
@@ -99,7 +108,7 @@
     ): List<List<TileViewModel>> {
 
         return PaginatableGridLayout.splitInRows(
-                tiles.map { SizedTile(it, it.spec.width()) },
+                tiles.map { SizedTileImpl(it, it.spec.width()) },
                 columns,
             )
             .chunked(rows)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 9b4d10f..af3803b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -52,7 +52,7 @@
         ) { index ->
             Tile(
                 tile = tiles[index],
-                iconOnly = sizedTiles[index].width == 1,
+                iconOnly = sizedTiles[index].isIcon,
                 modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index cb9d0f6..7e6ccd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -53,7 +53,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyGridScope
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.rememberScrollState
@@ -98,7 +97,8 @@
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.model.toTileGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -107,12 +107,10 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.res.R
 import java.util.function.Supplier
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 
 object TileType
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @Composable
 fun Tile(
     tile: TileViewModel,
@@ -286,15 +284,14 @@
 
 @Composable
 fun DefaultEditTileGrid(
-    tiles: List<EditTileViewModel>,
-    isIconOnly: (TileSpec) -> Boolean,
+    sizedTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
     onRemoveTile: (TileSpec) -> Unit,
-    onResize: (TileSpec, Boolean) -> Unit,
+    onResize: (TileSpec) -> Unit,
 ) {
-    val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+    val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
     val currentListState = rememberEditListState(currentTiles)
     val dragAndDropState = rememberDragAndDropState(currentListState)
 
@@ -304,9 +301,6 @@
     val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
         onAddTile(tileSpec, position)
     }
-    val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
-        onResize(tileSpec, !isIconOnly(tileSpec))
-    }
     val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
 
     CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
@@ -332,9 +326,8 @@
                 currentListState.tiles,
                 columns,
                 tilePadding,
-                isIconOnly,
                 onRemoveTile,
-                onDoubleTap,
+                onResize,
                 dragAndDropState,
                 onDropAdd,
             )
@@ -422,48 +415,32 @@
 
 @Composable
 private fun CurrentTilesGrid(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     tilePadding: Dp,
-    isIconOnly: (TileSpec) -> Boolean,
     onClick: (TileSpec) -> Unit,
-    onDoubleTap: (TileSpec) -> Unit,
+    onResize: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
     onDrop: (TileSpec, Int) -> Unit
 ) {
-    val tileHeight = tileHeight()
-    val currentRows =
-        remember(tiles) {
-            calculateRows(
-                tiles.map {
-                    SizedTile(
-                        it,
-                        if (isIconOnly(it.tileSpec)) {
-                            1
-                        } else {
-                            2
-                        }
-                    )
-                },
-                columns
-            )
-        }
-    val currentGridHeight = gridHeight(currentRows, tileHeight, tilePadding)
     // Current tiles
     CurrentTilesContainer {
+        val cells = tiles.toTileGridCells(columns)
+        val tileHeight = tileHeight()
+        val totalRows = cells.lastOrNull()?.row ?: 0
+        val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
         TileLazyGrid(
             modifier =
-                Modifier.height(currentGridHeight)
+                Modifier.height(totalHeight)
                     .dragAndDropTileList(dragAndDropState, { true }, onDrop),
             columns = GridCells.Fixed(columns)
         ) {
             editTiles(
-                tiles,
+                cells,
                 ClickAction.REMOVE,
                 onClick,
-                isIconOnly,
                 dragAndDropState,
-                onDoubleTap = onDoubleTap,
+                onResize = onResize,
                 indicatePosition = true,
                 acceptDrops = { true },
                 onDrop = onDrop,
@@ -474,13 +451,15 @@
 
 @Composable
 private fun AvailableTileGrid(
-    tiles: List<EditTileViewModel>,
+    tiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     tilePadding: Dp,
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
 ) {
-    val (otherTilesStock, otherTilesCustom) = tiles.partition { it.appName == null }
+    // Available tiles aren't visible during drag and drop, so the row isn't needed
+    val (otherTilesStock, otherTilesCustom) =
+        tiles.map { TileGridCell(it, 0) }.partition { it.tile.appName == null }
     val availableTileHeight = tileHeight(true)
     val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
 
@@ -493,7 +472,6 @@
             otherTilesStock,
             ClickAction.ADD,
             onClick,
-            isIconOnly = { true },
             dragAndDropState = dragAndDropState,
             acceptDrops = { false },
             showLabels = true,
@@ -502,7 +480,6 @@
             otherTilesCustom,
             ClickAction.ADD,
             onClick,
-            isIconOnly = { true },
             dragAndDropState = dragAndDropState,
             acceptDrops = { false },
             showLabels = true,
@@ -519,52 +496,27 @@
     return ((tileHeight + padding) * rows) - padding
 }
 
-private fun calculateRows(tiles: List<SizedTile<EditTileViewModel>>, columns: Int): Int {
-    val row = TileRow<EditTileViewModel>(columns)
-    var count = 0
-
-    for (tile in tiles) {
-        if (row.maybeAddTile(tile)) {
-            if (row.isFull()) {
-                // Row is full, no need to stretch tiles
-                count += 1
-                row.clear()
-            }
-        } else {
-            count += 1
-            row.clear()
-            row.maybeAddTile(tile)
-        }
-    }
-    if (row.tiles.isNotEmpty()) {
-        count += 1
-    }
-    return count
-}
-
 fun LazyGridScope.editTiles(
-    tiles: List<EditTileViewModel>,
+    cells: List<TileGridCell>,
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
-    isIconOnly: (TileSpec) -> Boolean,
     dragAndDropState: DragAndDropState,
     acceptDrops: (TileSpec) -> Boolean,
-    onDoubleTap: (TileSpec) -> Unit = {},
+    onResize: (TileSpec) -> Unit = {},
     onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
     showLabels: Boolean = false,
     indicatePosition: Boolean = false,
 ) {
     items(
-        count = tiles.size,
-        key = { tiles[it].tileSpec.spec },
-        span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+        count = cells.size,
+        key = { cells[it].key },
+        span = { cells[it].span },
         contentType = { TileType }
     ) { index ->
-        val viewModel = tiles[index]
-        val iconOnly = isIconOnly(viewModel.tileSpec)
-        val tileHeight = tileHeight(iconOnly && showLabels)
+        val cell = cells[index]
+        val tileHeight = tileHeight(cell.isIcon && showLabels)
 
-        if (!dragAndDropState.isMoving(viewModel.tileSpec)) {
+        if (!dragAndDropState.isMoving(cell.tile.tileSpec)) {
             val onClickActionName =
                 when (clickAction) {
                     ClickAction.ADD ->
@@ -579,8 +531,8 @@
                     ""
                 }
             EditTile(
-                tileViewModel = viewModel,
-                iconOnly = iconOnly,
+                tileViewModel = cell.tile,
+                iconOnly = cell.isIcon,
                 showLabels = showLabels,
                 modifier =
                     Modifier.height(tileHeight)
@@ -589,11 +541,11 @@
                             onClick(onClickActionName) { false }
                             this.stateDescription = stateDescription
                         }
-                        .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop)
+                        .dragAndDropTile(dragAndDropState, cell.tile.tileSpec, acceptDrops, onDrop)
                         .dragAndDropTileSource(
-                            viewModel,
+                            cell,
                             onClick,
-                            onDoubleTap,
+                            onResize,
                             dragAndDropState,
                         )
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
new file mode 100644
index 0000000..c241fd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.model
+
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Immutable
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+
+/**
+ * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's
+ * positioned at
+ */
+@Immutable
+data class TileGridCell(
+    override val tile: EditTileViewModel,
+    val row: Int,
+    val key: String = "${tile.tileSpec.spec}-$row",
+    override val width: Int,
+) : SizedTile<EditTileViewModel> {
+    constructor(
+        sizedTile: SizedTile<EditTileViewModel>,
+        row: Int
+    ) : this(
+        tile = sizedTile.tile,
+        row = row,
+        width = sizedTile.width,
+    )
+
+    val span = GridItemSpan(width)
+}
+
+fun List<SizedTile<EditTileViewModel>>.toTileGridCells(columns: Int): List<TileGridCell> {
+    return splitInRowsSequence(this, columns)
+        .flatMapIndexed { index, sizedTiles -> sizedTiles.map { TileGridCell(it, index) } }
+        .toList()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 8d2d74a..b604e18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -20,17 +20,22 @@
 import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 interface IconTilesViewModel {
+    val largeTiles: StateFlow<Set<TileSpec>>
+
     fun isIconTile(spec: TileSpec): Boolean
 
-    fun resize(spec: TileSpec, toIcon: Boolean)
+    fun resize(spec: TileSpec)
 }
 
 @SysUISingleton
 class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
     IconTilesViewModel {
+    override val largeTiles = interactor.largeTilesSpecs
+
     override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
 
-    override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
+    override fun resize(spec: TileSpec) = interactor.resize(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index bb00494..eee905f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -20,7 +20,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
 import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -59,7 +60,12 @@
             .flatMapLatest { columns ->
                 tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
                     tiles
-                        .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+                        .map {
+                            SizedTileImpl(
+                                TileViewModel(it.tile, it.spec),
+                                it.spec.width,
+                            )
+                        }
                         .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
                 }
             }
@@ -67,7 +73,12 @@
                 applicationScope,
                 SharingStarted.WhileSubscribed(),
                 tilesInteractor.currentTiles.value
-                    .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+                    .map {
+                        SizedTileImpl(
+                            TileViewModel(it.tile, it.spec),
+                            it.spec.width,
+                        )
+                    }
                     .let {
                         splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
                     }
@@ -75,26 +86,4 @@
 
     private val TileSpec.width: Int
         get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
-
-    companion object {
-        private fun splitInRowsSequence(
-            tiles: List<SizedTile<TileViewModel>>,
-            columns: Int,
-        ): Sequence<List<SizedTile<TileViewModel>>> = sequence {
-            val row = TileRow<TileViewModel>(columns)
-            for (tile in tiles) {
-                check(tile.width <= columns)
-                if (!row.maybeAddTile(tile)) {
-                    // Couldn't add tile to previous row, create a row with the current tiles
-                    // and start a new one
-                    yield(row.tiles)
-                    row.clear()
-                    row.maybeAddTile(tile)
-                }
-            }
-            if (row.tiles.isNotEmpty()) {
-                yield(row.tiles)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
index 56270ce..a42bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
@@ -81,9 +81,11 @@
                         }
                 setOnClickListener { showCategorySelector(this) }
             }
+            val attachToBRLabel = context.getString(T.string.attach_to_bug_report)
             requireViewById<Switch>(R.id.attach_to_bugreport_switch).apply {
                 isChecked = builder.attachToBugreport
                 setOnCheckedChangeListener { _, isChecked -> builder.attachToBugreport = isChecked }
+                contentDescription = attachToBRLabel
             }
             requireViewById<TextView>(R.id.cpu_buffer_size).setupSingleChoiceText(
                 T.array.buffer_size_values,
@@ -111,6 +113,7 @@
                 ) {
                     builder.maxLongTraceDurationMinutes = it
                 }
+            val longTracesLabel = context.getString(T.string.long_traces)
             requireViewById<Switch>(R.id.long_traces_switch).apply {
                 isChecked = builder.longTrace
                 val disabledAlpha by lazy { getDisabledAlpha(context) }
@@ -127,23 +130,24 @@
                     longTraceDurationText.alpha = newAlpha
                     longTraceSizeText.alpha = newAlpha
                 }
+                contentDescription = longTracesLabel
             }
+            val winscopeLabel = context.getString(T.string.winscope_tracing)
             requireViewById<Switch>(R.id.winscope_switch).apply {
                 isChecked = builder.winscope
                 setOnCheckedChangeListener { _, isChecked -> builder.winscope = isChecked }
+                contentDescription = winscopeLabel
             }
+            val debuggableAppsLabel = context.getString(T.string.trace_debuggable_applications)
             requireViewById<Switch>(R.id.trace_debuggable_apps_switch).apply {
                 isChecked = builder.apps
                 setOnCheckedChangeListener { _, isChecked -> builder.apps = isChecked }
+                contentDescription = debuggableAppsLabel
             }
-            requireViewById<TextView>(R.id.long_traces_switch_label).text =
-                context.getString(T.string.long_traces)
-            requireViewById<TextView>(R.id.debuggable_apps_switch_label).text =
-                context.getString(T.string.trace_debuggable_applications)
-            requireViewById<TextView>(R.id.winscope_switch_label).text =
-                context.getString(T.string.winscope_tracing)
-            requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text =
-                context.getString(T.string.attach_to_bug_report)
+            requireViewById<TextView>(R.id.long_traces_switch_label).text = longTracesLabel
+            requireViewById<TextView>(R.id.debuggable_apps_switch_label).text = debuggableAppsLabel
+            requireViewById<TextView>(R.id.winscope_switch_label).text = winscopeLabel
+            requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text = attachToBRLabel
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cbb61b3..1170354 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -181,7 +181,7 @@
                     mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
                 } else {
                     updateState(false);
-                    createErrorNotification();
+                    createErrorStartingNotification();
                     stopForeground(STOP_FOREGROUND_DETACH);
                     stopSelf();
                     return Service.START_NOT_STICKY;
@@ -272,17 +272,30 @@
     }
 
     /**
-     * Simple error notification, needed since startForeground must be called to avoid errors
+     * Simple "error starting" notification, needed since startForeground must be called to avoid
+     * errors.
      */
     @VisibleForTesting
-    protected void createErrorNotification() {
+    protected void createErrorStartingNotification() {
+        createErrorNotification(strings().getStartError());
+    }
+
+    /**
+     * Simple "error saving" notification, needed since startForeground must be called to avoid
+     * errors.
+     */
+    @VisibleForTesting
+    protected void createErrorSavingNotification() {
+        createErrorNotification(strings().getSaveError());
+    }
+
+    private void createErrorNotification(String notificationContentTitle) {
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
-        String notificationTitle = strings().getStartError();
 
         Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(notificationTitle)
+                .setContentTitle(notificationContentTitle)
                 .addExtras(extras);
         startForeground(mNotificationId, builder.build());
     }
@@ -427,11 +440,11 @@
                 // let's release the recorder and delete all temporary files in this case
                 getRecorder().release();
             }
-            showErrorToast(R.string.screenrecord_start_error);
+            showErrorToast(R.string.screenrecord_save_error);
             Log.e(getTag(), "stopRecording called, but there was an error when ending"
                     + "recording");
             exception.printStackTrace();
-            createErrorNotification();
+            createErrorSavingNotification();
         } catch (Throwable throwable) {
             if (getRecorder() != null) {
                 // Something unexpected happen, SystemUI will crash but let's delete
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 8b88da1..348b6ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.lifecycle.lifecycleScope
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.fragments.FragmentService
@@ -191,11 +190,7 @@
     }
 
     private fun calculateLargeShadeHeaderHeight(): Int {
-        return if (centralizedStatusBarHeightFix()) {
-            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-        } else {
-            resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
-        }
+        return largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
     }
 
     private fun calculateShadeHeaderHeight(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 9f61d4e..0a092a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -19,7 +19,6 @@
 
 import static android.view.WindowInsets.Type.ime;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
 import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
@@ -444,10 +443,7 @@
         mUseLargeScreenShadeHeader =
                 LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
         mLargeScreenShadeHeaderHeight =
-                centralizedStatusBarHeightFix()
-                        ? mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                        : mResources.getDimensionPixelSize(
-                                R.dimen.large_screen_shade_header_height);
+                mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight();
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 37da114..c49cfbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -231,10 +230,12 @@
     private val demoModeReceiver =
         object : DemoMode {
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+
             override fun dispatchDemoCommand(command: String, args: Bundle) =
                 clock.dispatchDemoCommand(command, args)
 
             override fun onDemoModeStarted() = clock.onDemoModeStarted()
+
             override fun onDemoModeFinished() = clock.onDemoModeFinished()
         }
 
@@ -442,9 +443,7 @@
             changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
         }
 
-        if (centralizedStatusBarHeightFix()) {
-            view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
-        }
+        view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
         view.updateAllConstraints(changes)
         updateBatteryMode()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
index e1289af..2f98488 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -29,7 +32,11 @@
 @Inject
 constructor(
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
 ) {
     val isKeyguardOccluded: Flow<Boolean> =
-        keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f }
+        anyOf(
+            keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
+            keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cea97d6..50be6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,7 +19,6 @@
 import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
@@ -1219,7 +1218,7 @@
                 && mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
             // Set previous NavBar's IME window status as invisible when IME
             // window switched to another display for single-session IME case.
-            sendImeInvisibleStatusForPrevNavBar();
+            sendImeNotVisibleStatusForPrevNavBar();
         }
         for (int i = 0; i < mCallbacks.size(); i++) {
             mCallbacks.get(i).setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher);
@@ -1227,9 +1226,9 @@
         mLastUpdatedImeDisplayId = displayId;
     }
 
-    private void sendImeInvisibleStatusForPrevNavBar() {
+    private void sendImeNotVisibleStatusForPrevNavBar() {
         for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, IME_INVISIBLE,
+            mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, 0 /* vis */,
                     BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 6ba4fef..9e6cacb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -64,7 +65,8 @@
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
 
-    private val internalChip =
+    /** A direct mapping from [ScreenRecordChipModel] to [OngoingActivityChipModel]. */
+    private val simpleChip =
         interactor.screenRecordState
             .map { state ->
                 when (state) {
@@ -105,10 +107,31 @@
             // See b/347726238 for [SharingStarted.Lazily] reasoning.
             .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
+    /**
+     * The screen record chip to show that also ensures that the start time doesn't change once we
+     * enter the recording state. If we change the start time while we're recording, the chronometer
+     * could skip a second. See b/349620526.
+     */
+    private val chipWithConsistentTimer: StateFlow<OngoingActivityChipModel> =
+        simpleChip
+            .pairwise(initialValue = OngoingActivityChipModel.Hidden())
+            .map { (old, new) ->
+                if (
+                    old is OngoingActivityChipModel.Shown.Timer &&
+                        new is OngoingActivityChipModel.Shown.Timer
+                ) {
+                    new.copy(startTimeMs = old.startTimeMs)
+                } else {
+                    new
+                }
+            }
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
     private val chipTransitionHelper = ChipTransitionHelper(scope)
 
     override val chip: StateFlow<OngoingActivityChipModel> =
-        chipTransitionHelper.createChipFlow(internalChip)
+        chipTransitionHelper.createChipFlow(chipWithConsistentTimer)
 
     private fun createDelegate(
         recordedTask: ActivityManager.RunningTaskInfo?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 130b117..8a5165d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.ColorStateList
-import android.view.ContextThemeWrapper
 import androidx.annotation.ColorInt
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
@@ -43,9 +42,7 @@
     /** The chip should have a red background with white text. */
     data object Red : ColorsModel {
         override fun background(context: Context): ColorStateList {
-            val themedContext =
-                ContextThemeWrapper(context, com.android.internal.R.style.Theme_DeviceDefault_Light)
-            return Utils.getColorAttr(themedContext, com.android.internal.R.attr.materialColorError)
+            return ColorStateList.valueOf(context.getColor(R.color.GM2_red_700))
         }
 
         override fun text(context: Context) = context.getColor(android.R.color.white)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
index 0d5ade7..c7b3c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
@@ -103,7 +103,7 @@
         mSwipeDistanceThreshold =
             r.getDimensionPixelSize(R.dimen.system_gestures_distance_threshold)
         val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId)
-        val displayCutout = display.cutout
+        val displayCutout = display?.cutout
         if (displayCutout != null) {
             // Expand swipe start threshold such that we can catch touches that just start beyond
             // the notch area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 9b21fa9..2537aff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
 import android.content.Context
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
@@ -75,11 +74,7 @@
                             getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
                         marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
                         marginTopLargeScreen =
-                            if (centralizedStatusBarHeightFix()) {
-                                largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                            } else {
-                                getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
-                            },
+                            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight(),
                         keyguardSplitShadeTopMargin =
                             getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index fd08e89..a30b877 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -17,14 +17,14 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import android.util.Log
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.view.onLayoutChanged
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
@@ -33,7 +33,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.launch
@@ -46,7 +45,7 @@
     dumpManager: DumpManager,
     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
     private val view: NotificationScrollView,
-    private val viewModel: NotificationScrollViewModel,
+    private val viewModelFactory: NotificationScrollViewModel.Factory,
     private val configuration: ConfigurationState,
 ) : FlowDumperImpl(dumpManager) {
 
@@ -61,38 +60,42 @@
     }
 
     fun bindWhileAttached(): DisposableHandle {
-        return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
-            repeatOnLifecycle(Lifecycle.State.CREATED) { bind() }
-        }
+        return view.asView().repeatWhenAttached(mainImmediateDispatcher) { bind() }
     }
 
-    suspend fun bind() = coroutineScope {
-        launchAndDispose {
-            updateViewPosition()
-            view.asView().onLayoutChanged { updateViewPosition() }
-        }
+    suspend fun bind(): Nothing =
+        view.asView().viewModel(
+            minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+            factory = viewModelFactory::create,
+        ) { viewModel ->
+            launchAndDispose {
+                updateViewPosition()
+                view.asView().onLayoutChanged { updateViewPosition() }
+            }
 
-        launch {
-            viewModel
-                .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
-                .collect { view.setScrimClippingShape(it) }
-        }
+            launch {
+                viewModel
+                    .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+                    .collect { view.setScrimClippingShape(it) }
+            }
 
-        launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
-        launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
-        launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
-        launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
-        launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
+            launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
+            launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
+            launch {
+                viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
+            }
+            launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
+            launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
 
-        launchAndDispose {
-            view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
-            view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
-            DisposableHandle {
-                view.setSyntheticScrollConsumer(null)
-                view.setCurrentGestureOverscrollConsumer(null)
+            launchAndDispose {
+                view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
+                view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
+                DisposableHandle {
+                    view.setSyntheticScrollConsumer(null)
+                    view.setCurrentGestureOverscrollConsumer(null)
+                }
             }
         }
-    }
 
     /** flow of the scrim clipping radius */
     private val scrimRadius: Flow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 2ba79a8..4281025 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -19,9 +19,9 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -33,9 +33,11 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
+import com.android.systemui.util.kotlin.FlowDumper
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import dagger.Lazy
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -43,9 +45,8 @@
 import kotlinx.coroutines.flow.map
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
-@SysUISingleton
 class NotificationScrollViewModel
-@Inject
+@AssistedInject
 constructor(
     dumpManager: DumpManager,
     stackAppearanceInteractor: NotificationStackAppearanceInteractor,
@@ -54,7 +55,9 @@
     // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
     // while the flag is off, creating this object too early results in a crash
     keyguardInteractor: Lazy<KeyguardInteractor>,
-) : FlowDumperImpl(dumpManager) {
+) : FlowDumper by FlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
+    SysUiViewModel() {
+
     /**
      * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
      * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
@@ -186,4 +189,9 @@
             keyguardInteractor.get().isDozing.dumpWhileCollecting("isDozing")
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationScrollViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2c7ce00..b6d58d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2269,7 +2269,10 @@
         // applying the dimming effect twice.
         mUiBgExecutor.execute(() -> {
             float dimAmount = 0f;
-            if (mWallpaperManager.lockScreenWallpaperExists()) {
+            // Note that access to WallpaperManager APIs should be guarded by a check into
+            // WallpaperManager#isWallpaperSupported. Form factors that do not use wallpaper
+            // may crash SysUI during improper access. ref: b/355307617
+            if (!mWallpaperSupported || mWallpaperManager.lockScreenWallpaperExists()) {
                 dimAmount = mWallpaperManager.getWallpaperDimAmount();
             }
             final float scrimDimAmount = dimAmount;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 0adc1b0..013903a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale;
 import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
@@ -169,9 +168,7 @@
         mStatusViewBottomMargin =
                 res.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin);
         mSplitShadeTopNotificationsMargin =
-                centralizedStatusBarHeightFix()
-                        ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context)
-                        : res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context);
         mSplitShadeTargetTopMargin =
                 res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 84e6018..c3da7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
 
@@ -133,9 +132,6 @@
         mUserSwitcherContainer = findViewById(R.id.user_switcher_container);
         mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
         loadDimens();
-        if (!centralizedStatusBarHeightFix()) {
-            setGravity(Gravity.CENTER_VERTICAL);
-        }
     }
 
     /**
@@ -322,7 +318,7 @@
         final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
                 ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right;
 
-        int top = centralizedStatusBarHeightFix() ? waterfallTop + mPadding.top : waterfallTop;
+        int top = waterfallTop + mPadding.top;
         setPadding(minLeft, top, minRight, 0);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
new file mode 100644
index 0000000..51b14c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
+
+@Composable
+fun HomeGestureTutorialScreen(
+    onDoneButtonClicked: () -> Unit,
+    onBack: () -> Unit,
+) {
+    val screenConfig =
+        TutorialScreenConfig(
+            colors = rememberScreenColors(),
+            strings =
+                TutorialScreenConfig.Strings(
+                    titleResId = R.string.touchpad_home_gesture_action_title,
+                    bodyResId = R.string.touchpad_home_gesture_guidance,
+                    titleSuccessResId = R.string.touchpad_home_gesture_done,
+                    bodySuccessResId = R.string.touchpad_home_gesture_finished
+                ),
+            animations =
+                TutorialScreenConfig.Animations(
+                    educationResId = R.raw.trackpad_home_edu,
+                    successResId = R.raw.trackpad_home_success
+                )
+        )
+    val gestureMonitorProvider =
+        object : GestureMonitorProvider {
+            override fun createGestureMonitor(
+                gestureDistanceThresholdPx: Int,
+                gestureStateChangedCallback: (GestureState) -> Unit
+            ): TouchpadGestureMonitor {
+                return HomeGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+            }
+        }
+    GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
+}
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+    val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
+    val onPrimaryFixed = LocalAndroidColorScheme.current.onPrimaryFixed
+    val onPrimaryFixedVariant = LocalAndroidColorScheme.current.onPrimaryFixedVariant
+    val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
+    val dynamicProperties =
+        rememberLottieDynamicProperties(
+            rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
+            rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
+            rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+        )
+    val screenColors =
+        remember(surfaceContainer, dynamicProperties) {
+            TutorialScreenConfig.Colors(
+                background = onPrimaryFixed,
+                successBackground = surfaceContainer,
+                title = primaryFixedDim,
+                animationColors = dynamicProperties,
+            )
+        }
+    return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 088a8fd..ad8ab30 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -28,6 +28,7 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
 import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
@@ -78,6 +79,10 @@
                 onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
                 onBack = { vm.goTo(TUTORIAL_SELECTION) },
             )
-        HOME_GESTURE -> {}
+        HOME_GESTURE ->
+            HomeGestureTutorialScreen(
+                onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
+                onBack = { vm.goTo(TUTORIAL_SELECTION) },
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index d757e33..36468144 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -19,6 +19,7 @@
 
 import android.annotation.UserIdInt
 import android.database.ContentObserver
+import com.android.systemui.Flags
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -39,9 +40,21 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) }
+            names.forEach { name ->
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    registerContentObserverForUser(name, observer, userId)
+                } else {
+                    registerContentObserverForUserSync(name, observer, userId)
+                }
+            }
 
-            awaitClose { unregisterContentObserverSync(observer) }
+            awaitClose {
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    unregisterContentObserverAsync(observer)
+                } else {
+                    unregisterContentObserverSync(observer)
+                }
+            }
         }
     }
 
@@ -57,9 +70,21 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserverSync(name, observer) }
+            names.forEach { name ->
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    registerContentObserver(name, observer)
+                } else {
+                    registerContentObserverSync(name, observer)
+                }
+            }
 
-            awaitClose { unregisterContentObserverSync(observer) }
+            awaitClose {
+                if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+                    unregisterContentObserverAsync(observer)
+                } else {
+                    unregisterContentObserverSync(observer)
+                }
+            }
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 16b9ab5..ff47fd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -48,6 +48,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
@@ -102,6 +103,8 @@
     private AccessibilityLogger mA11yLogger;
     @Mock
     private IWindowManager mIWindowManager;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     private IMagnificationConnection mIMagnificationConnection;
     private MagnificationImpl mMagnification;
@@ -123,7 +126,8 @@
                 mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class),
-                mA11yLogger, mIWindowManager, mAccessibilityManager);
+                mA11yLogger, mIWindowManager, mAccessibilityManager,
+                mViewCaptureAwareWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier =
                 new FakeWindowMagnificationControllerSupplier(
                         mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 5be1180..1ceac78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -73,10 +73,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
 
+import kotlin.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -104,6 +108,8 @@
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private MagnificationModeSwitch.ClickListener mClickListener;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
     private TestableWindowManager mWindowManager;
     private ViewPropertyAnimator mViewPropertyAnimator;
     private MagnificationModeSwitch mMagnificationModeSwitch;
@@ -133,8 +139,10 @@
             return null;
         }).when(mSfVsyncFrameProvider).postFrameCallback(
                 any(Choreographer.FrameCallback.class));
+        ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, false);
         mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView,
-                mSfVsyncFrameProvider, mClickListener);
+                mSfVsyncFrameProvider, mClickListener, vwm);
         assertNotNull(mTouchListener);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index d0f8e78..3cd3fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -27,6 +27,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
@@ -56,6 +57,8 @@
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     @Before
     public void setUp() {
@@ -63,7 +66,7 @@
         mMagnificationSettingsController = new MagnificationSettingsController(
                 mContext, mSfVsyncFrameProvider,
                 mMagnificationSettingControllerCallback, mSecureSettings,
-                mWindowMagnificationSettings);
+                mWindowMagnificationSettings, mViewCaptureAwareWindowManager);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 038b81b..057ddcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -50,6 +50,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
@@ -96,6 +97,8 @@
     private AccessibilityLogger mA11yLogger;
     @Mock
     private IWindowManager mIWindowManager;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     @Before
     public void setUp() throws Exception {
@@ -129,7 +132,8 @@
                 mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
                 getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
-                getContext().getSystemService(AccessibilityManager.class));
+                getContext().getSystemService(AccessibilityManager.class),
+                mViewCaptureAwareWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index 6e94297..e1e515e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -28,6 +28,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.After;
@@ -50,6 +51,8 @@
     private View mSpyView;
     @Mock
     private MagnificationModeSwitch.ClickListener mListener;
+    @Mock
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
 
     @Before
@@ -58,7 +61,8 @@
         mSupplier = new FakeSwitchSupplier(mContext.getSystemService(DisplayManager.class));
         mModeSwitchesController = new ModeSwitchesController(mSupplier);
         mModeSwitchesController.setClickListenerDelegate(mListener);
-        mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+        mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController,
+                mViewCaptureAwareWindowManager));
         mSpyView = Mockito.spy(new View(mContext));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 003f7e4..9507077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -61,6 +61,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
@@ -68,6 +70,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -95,6 +99,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
     private TestableWindowManager mWindowManager;
     private WindowMagnificationSettings mWindowMagnificationSettings;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@@ -119,9 +125,11 @@
         when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
                 returnsSecondArg());
 
+        ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, /* isViewCaptureEnabled= */ false);
         mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
                 mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider,
-                mSecureSettings);
+                mSecureSettings, vwm);
 
         mSettingView = mWindowMagnificationSettings.getSettingView();
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 8246506..74bc928 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -208,8 +208,8 @@
         with(kosmos) {
             testScope.runTest {
                 whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-                whenever(cachedBluetoothDevice.connectableProfiles)
-                        .thenReturn(listOf(leAudioProfile))
+                whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+                    .thenReturn(listOf(leAudioProfile))
 
                 whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
                 whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -243,8 +243,8 @@
             testScope.runTest {
                 whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
                 whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
-                whenever(cachedBluetoothDevice.connectableProfiles)
-                        .thenReturn(listOf(leAudioProfile))
+                whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+                    .thenReturn(listOf(leAudioProfile))
 
                 whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
                 whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -254,12 +254,12 @@
 
                 whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
                 whenever(
-                    BluetoothUtils.hasConnectedBroadcastSource(
-                        ArgumentMatchers.any(),
-                        ArgumentMatchers.any()
+                        BluetoothUtils.hasConnectedBroadcastSource(
+                            ArgumentMatchers.any(),
+                            ArgumentMatchers.any()
+                        )
                     )
-                )
-                        .thenReturn(false)
+                    .thenReturn(false)
 
                 actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
                 verify(activityStarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 20cb1e1..0ac04b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -456,8 +457,20 @@
             assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
         }
 
+    @Test
+    fun displayFlow_emitsCorrectDisplaysAtFirst() =
+        testScope.runTest {
+            setDisplays(0, 1, 2)
+
+            val values: List<Set<Display>> by collectValues(displayRepository.displays)
+
+            assertThat(values.toIdSets()).containsExactly(setOf(0, 1, 2))
+        }
+
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
 
+    private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
+
     // Wrapper to capture the displayListener.
     private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
         val flowValue = collectLastValue(displayRepository.displays)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index a73df07..9797c8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -104,6 +104,7 @@
             WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
             /* appearance= */ 0,
             /* isTranslucent= */ false,
-            /* hasImeSurface= */ false
+            /* hasImeSurface= */ false,
+            /* uiMode */ 0
         )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index a8cbbd4..a52ab0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -20,7 +20,6 @@
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -512,7 +511,7 @@
 
         externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE,
                 BACK_DISPOSITION_DEFAULT, true);
-        defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_INVISIBLE,
+        defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */,
                 BACK_DISPOSITION_DEFAULT, false);
         // Verify IME window state will be updated in external NavBar & default NavBar state reset.
         assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index f866f74..79c206c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -126,7 +126,8 @@
         doNothing().when(mRecordingService).createRecordingNotification();
         doReturn(mNotification).when(mRecordingService).createProcessingNotification();
         doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
-        doNothing().when(mRecordingService).createErrorNotification();
+        doNothing().when(mRecordingService).createErrorStartingNotification();
+        doNothing().when(mRecordingService).createErrorSavingNotification();
         doNothing().when(mRecordingService).showErrorToast(anyInt());
         doNothing().when(mRecordingService).stopForeground(anyInt());
 
@@ -234,7 +235,7 @@
 
         mRecordingService.onStopped();
 
-        verify(mRecordingService).createErrorNotification();
+        verify(mRecordingService).createErrorSavingNotification();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 2c2fcbe..13bc82f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -28,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
@@ -166,31 +164,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
-        val headerResourceHeight = 20
-        val headerHelperHeight = 30
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(headerHelperHeight)
-        overrideResource(R.bool.config_use_large_screen_shade_header, true)
-        overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
-
-        // ensure the estimated height (would be 3 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
-        underTest.updateResources()
-
-        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
-        verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
-            .isEqualTo(headerResourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+    fun testLargeScreen_updateResources_splitShadeHeightIsSetBasedOnHelper() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -447,31 +421,8 @@
     }
 
     @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
-        setLargeScreen()
-        val largeScreenHeaderResourceHeight = 100
-        val largeScreenHeaderHelperHeight = 200
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(largeScreenHeaderHelperHeight)
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
-        // ensure the estimated height (would be 30 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
-        underTest.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-    }
-
-    @Test
     @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index f21def3..4850b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade
 
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -28,7 +27,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
@@ -164,29 +162,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
-        val helperHeight = 30
-        val resourceHeight = 20
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
-        overrideResource(R.bool.config_use_large_screen_shade_header, true)
-        overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
-
-        // ensure the estimated height (would be 3 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
-        underTest.updateResources()
-
-        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
-        verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet_basedOnHelper() {
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -427,28 +403,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
-        setLargeScreen()
-        val largeScreenHeaderHelperHeight = 200
-        val largeScreenHeaderResourceHeight = 100
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(largeScreenHeaderHelperHeight)
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
-        // ensure the estimated height (would be 30 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
-        underTest.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderResourceHeight)
-    }
-
-    @Test
-    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
-    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 86d21e8..6916bbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -16,7 +16,6 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
@@ -207,7 +206,7 @@
 
         mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true);
         waitForIdleSync();
-        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_INVISIBLE),
+        verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0),
                 eq(BACK_DISPOSITION_DEFAULT), eq(false));
         verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index e68fa0b..804eb5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -231,6 +231,34 @@
             assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
         }
 
+    /** Regression test for b/349620526. */
+    @Test
+    fun chip_recordingState_thenGetsTaskInfo_startTimeDoesNotChange() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            // Start recording, but without any task info
+            systemClock.setElapsedRealtime(1234)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+            // WHEN we receive the recording task info a few milliseconds later
+            systemClock.setElapsedRealtime(1240)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    "host.package",
+                    hostDeviceName = null,
+                    FakeActivityTaskManager.createTask(taskId = 1)
+                )
+
+            // THEN the start time is still the old start time
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+        }
+
     @Test
     fun chip_notProjecting_clickListenerShowsDialog() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 7f33c23..eb1e28b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,13 +26,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.log.LogBuffer;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index edf4bcc..1d2bce2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,10 +17,9 @@
 package com.android.systemui.education.data.repository
 
 import com.android.systemui.kosmos.Kosmos
-import java.time.Clock
 import java.time.Instant
 
 var Kosmos.contextualEducationRepository: ContextualEducationRepository by
-    Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }
+    Kosmos.Fixture { FakeContextualEducationRepository() }
 
-var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
+var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 3816e1b..aa1968a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -18,12 +18,11 @@
 
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+class FakeContextualEducationRepository : ContextualEducationRepository {
 
     private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
     private val _gestureEduModels = MutableStateFlow(GestureEduModel())
@@ -44,16 +43,11 @@
         return gestureEduModelsFlow
     }
 
-    override suspend fun incrementSignalCount(gestureType: GestureType) {
-        val originalModel = _gestureEduModels.value
-        _gestureEduModels.value =
-            originalModel.copy(
-                signalCount = _gestureEduModels.value.signalCount + 1,
-            )
-    }
-
-    override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
-        val originalModel = _gestureEduModels.value
-        _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
+    override suspend fun updateGestureEduModel(
+        gestureType: GestureType,
+        transform: (GestureEduModel) -> GestureEduModel
+    ) {
+        val currentModel = _gestureEduModels.value
+        _gestureEduModels.value = transform(currentModel)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
index 513c143..c9a5d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -19,8 +19,9 @@
 import java.time.Clock
 import java.time.Instant
 import java.time.ZoneId
+import kotlin.time.Duration
 
-class FakeEduClock(private val base: Instant) : Clock() {
+class FakeEduClock(private var base: Instant) : Clock() {
     private val zone: ZoneId = ZoneId.of("UTC")
 
     override fun instant(): Instant {
@@ -34,4 +35,8 @@
     override fun getZone(): ZoneId {
         return zone
     }
+
+    fun offset(duration: Duration) {
+        base = base.plusSeconds(duration.inWholeSeconds)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index a7b322b..5c99a7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.education.domain.interactor
 
 import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -28,6 +29,7 @@
             backgroundScope = testScope.backgroundScope,
             backgroundDispatcher = testDispatcher,
             repository = contextualEducationRepository,
-            selectedUserInteractor = selectedUserInteractor
+            selectedUserInteractor = selectedUserInteractor,
+            clock = fakeEduClock
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index fb4e901..5088677 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.education.domain.interactor
 
+import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 
@@ -23,7 +24,8 @@
     Kosmos.Fixture {
         KeyboardTouchpadEduInteractor(
             backgroundScope = testScope.backgroundScope,
-            contextualEducationInteractor = contextualEducationInteractor
+            contextualEducationInteractor = contextualEducationInteractor,
+            clock = fakeEduClock
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
index cd4fab8..6252d44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
@@ -16,8 +16,14 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 
 val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by
-    Kosmos.Fixture { NotificationShadeWindowModel(keyguardTransitionInteractor) }
+    Kosmos.Fixture {
+        NotificationShadeWindowModel(
+            keyguardTransitionInteractor,
+            keyguardInteractor,
+        )
+    }
diff --git a/packages/overlays/HsumConfigOverlay/Android.bp b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
similarity index 92%
rename from packages/overlays/HsumConfigOverlay/Android.bp
rename to packages/overlays/HsumDefaultConfigOverlay/Android.bp
index 050b1f0..bff2f9b 100644
--- a/packages/overlays/HsumConfigOverlay/Android.bp
+++ b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
@@ -8,7 +8,7 @@
 }
 
 runtime_resource_overlay {
-    name: "HsumConfigOverlay",
+    name: "HsumDefaultConfigOverlay",
     certificate: "platform",
 
     product_specific: true,
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
similarity index 93%
rename from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
rename to packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
index cd7a879..dcd1741 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
@@ -15,7 +15,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.internal.overlay.hsumconfig"
+    package="com.android.internal.overlay.hsum.defaultconfig"
     android:versionCode="1"
     android:versionName="1.0">
     <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
diff --git a/packages/overlays/HsumConfigOverlay/OWNERS b/packages/overlays/HsumDefaultConfigOverlay/OWNERS
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/OWNERS
rename to packages/overlays/HsumDefaultConfigOverlay/OWNERS
diff --git a/packages/overlays/HsumConfigOverlay/res/values/config.xml b/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/res/values/config.xml
rename to packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
index da4dfa9..6c8a486 100644
--- a/proto/src/windowmanager.proto
+++ b/proto/src/windowmanager.proto
@@ -45,6 +45,7 @@
   int32 letterbox_inset_top = 18;
   int32 letterbox_inset_right = 19;
   int32 letterbox_inset_bottom = 20;
+  int32 ui_mode = 21;
 }
 
 // Persistent letterboxing configurations
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0ab6bbc..42f69e9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -223,8 +223,8 @@
             // delays (even in case of the Main Thread). It may be fine overall, but would require
             // updating the tests (adding a delay there).
             mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
-            mDevicePresenceProcessor.init(context);
         } else if (phase == PHASE_BOOT_COMPLETED) {
+            mDevicePresenceProcessor.init(context);
             // Run the Inactive Association Removal job service daily.
             InactiveAssociationsRemovalService.schedule(getContext());
             mCrossDeviceSyncController.onBootCompleted();
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3123268..0b6d135 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -51,6 +51,7 @@
 import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 
 /**
  * Maps system settings to system properties.
@@ -345,7 +346,7 @@
         // add sys prop sync callback for staged flag values
         DeviceConfig.addOnPropertiesChangedListener(
             NAMESPACE_REBOOT_STAGING,
-            AsyncTask.THREAD_POOL_EXECUTOR,
+            newSingleThreadScheduledExecutor(),
             (DeviceConfig.Properties properties) -> {
 
               for (String flagName : properties.getKeyset()) {
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 2cea32a..f050090 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -10,6 +10,9 @@
                     "include-filter": "android.media.audio.cts.AudioFocusTest"
                 },
                 {
+                    "include-filter": "android.media.audio.cts.AudioPlaybackCaptureTest"
+                },
+                {
                     "include-filter": "android.media.audio.cts.SpatializerTest"
                 }
             ]
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index fe73bfe..feef540 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -385,9 +385,9 @@
                         DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
                         userId) != 0);
             } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
-                updateMandatoryBiometricsForAllProfiles();
+                updateMandatoryBiometricsForAllProfiles(userId);
             } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
-                updateMandatoryBiometricsRequirementsForAllProfiles();
+                updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
         }
 
@@ -431,16 +431,15 @@
 
         public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
             if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
-                updateMandatoryBiometricsForAllProfiles();
+                updateMandatoryBiometricsForAllProfiles(userId);
             }
             if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
-                updateMandatoryBiometricsRequirementsForAllProfiles();
+                updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
             return mMandatoryBiometricsEnabled.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
-                    && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED)
                     && getEnabledForApps(userId)
                     && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
                     || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
@@ -455,25 +454,31 @@
             }
         }
 
-        private void updateMandatoryBiometricsForAllProfiles() {
-            final int mainUserId = mUserManager.getMainUser().getIdentifier();
-            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
-                mMandatoryBiometricsEnabled.put(userHandle.getIdentifier(),
+        private void updateMandatoryBiometricsForAllProfiles(int userId) {
+            int effectiveUserId = userId;
+            if (mUserManager.getMainUser() != null) {
+                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            }
+            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+                mMandatoryBiometricsEnabled.put(profileUserId,
                         Settings.Secure.getIntForUser(
                                 mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
                                 DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
-                                mainUserId) != 0);
+                                effectiveUserId) != 0);
             }
         }
 
-        private void updateMandatoryBiometricsRequirementsForAllProfiles() {
-            final int mainUserId = mUserManager.getMainUser().getIdentifier();
-            for (UserHandle userHandle: mUserManager.getUserProfiles()) {
-                mMandatoryBiometricsRequirementsSatisfied.put(userHandle.getIdentifier(),
+        private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
+            int effectiveUserId = userId;
+            if (mUserManager.getMainUser() != null) {
+                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            }
+            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+                mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
                         Settings.Secure.getIntForUser(mContentResolver,
                                 Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
                                 DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
-                                mainUserId) != 0);
+                                effectiveUserId) != 0);
             }
         }
 
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index d32a5ed..819b9a1 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.os.IBinder;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
@@ -227,4 +228,20 @@
      *     since boot.
      */
     public abstract int getLastUsedInputDeviceId();
+
+    /**
+     * Notify Keyboard system shortcut was triggered by the user and handled by the framework.
+     *
+     * NOTE: This is just to notify that a system shortcut was triggered. No further action is
+     * required to execute the said shortcut. This callback is meant for purposes of providing user
+     * hints or logging, etc.
+     *
+     * @param deviceId the device ID of the keyboard using which the shortcut was triggered
+     * @param keycodes the keys pressed for triggering the shortcut
+     * @param modifierState the modifier state of the key event that triggered the shortcut
+     * @param shortcut the shortcut that was triggered
+     *
+     */
+    public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes,
+            int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a06ad14..e555761 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
@@ -56,6 +57,7 @@
 import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -157,6 +159,7 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+    private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -306,6 +309,9 @@
     // Manages Sticky modifier state
     private final StickyModifierStateController mStickyModifierStateController;
 
+    // Manages keyboard system shortcut callbacks
+    private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler;
+
     // Manages Keyboard microphone mute led
     private final KeyboardLedController mKeyboardLedController;
 
@@ -461,6 +467,7 @@
                         injector.getLooper(), injector.getUEventManager())
                 : new KeyboardBacklightControllerInterface() {};
         mStickyModifierStateController = new StickyModifierStateController();
+        mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler();
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
                 mNative);
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -2703,6 +2710,36 @@
                 lockedModifierState);
     }
 
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void registerKeyboardSystemShortcutListener(
+            @NonNull IKeyboardSystemShortcutListener listener) {
+        super.registerKeyboardSystemShortcutListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener,
+                Binder.getCallingPid());
+    }
+
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+    public void unregisterKeyboardSystemShortcutListener(
+            @NonNull IKeyboardSystemShortcutListener listener) {
+        super.unregisterKeyboardSystemShortcutListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener,
+                Binder.getCallingPid());
+    }
+
+    private void handleKeyboardSystemShortcutTriggered(int deviceId,
+            KeyboardSystemShortcut shortcut) {
+        InputDevice device = getInputDevice(deviceId);
+        if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+            return;
+        }
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut);
+        mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut);
+    }
+
     /**
      * Callback interface implemented by the Window Manager.
      */
@@ -2871,6 +2908,10 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
+                case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED:
+                    int deviceId = msg.arg1;
+                    KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj;
+                    handleKeyboardSystemShortcutTriggered(deviceId, shortcut);
             }
         }
     }
@@ -3196,6 +3237,13 @@
         public int getLastUsedInputDeviceId() {
             return mNative.getLastUsedInputDeviceId();
         }
+
+        @Override
+        public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState,
+                @KeyboardSystemShortcut.SystemShortcut int shortcut) {
+            mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0,
+                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget();
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f21fd41..3d2f951 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -24,31 +24,25 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.role.RoleManager;
-import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InputDevice;
-import android.view.KeyEvent;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
 import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.policy.ModifierShortcutManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Collect Keyboard metrics
@@ -66,336 +60,20 @@
     @VisibleForTesting
     public static final String DEFAULT_LANGUAGE_TAG = "None";
 
-    public enum KeyboardLogEvent {
-        UNSPECIFIED(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
-                "INVALID_KEYBOARD_EVENT"),
-        HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
-                "HOME"),
-        RECENT_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
-                "RECENT_APPS"),
-        BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
-                "BACK"),
-        APP_SWITCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
-                "APP_SWITCH"),
-        LAUNCH_ASSISTANT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
-                "LAUNCH_ASSISTANT"),
-        LAUNCH_VOICE_ASSISTANT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
-                "LAUNCH_VOICE_ASSISTANT"),
-        LAUNCH_SYSTEM_SETTINGS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
-                "LAUNCH_SYSTEM_SETTINGS"),
-        TOGGLE_NOTIFICATION_PANEL(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
-                "TOGGLE_NOTIFICATION_PANEL"),
-        TOGGLE_TASKBAR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
-                "TOGGLE_TASKBAR"),
-        TAKE_SCREENSHOT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
-                "TAKE_SCREENSHOT"),
-        OPEN_SHORTCUT_HELPER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
-                "OPEN_SHORTCUT_HELPER"),
-        BRIGHTNESS_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
-                "BRIGHTNESS_UP"),
-        BRIGHTNESS_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
-                "BRIGHTNESS_DOWN"),
-        KEYBOARD_BACKLIGHT_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
-                "KEYBOARD_BACKLIGHT_UP"),
-        KEYBOARD_BACKLIGHT_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
-                "KEYBOARD_BACKLIGHT_DOWN"),
-        KEYBOARD_BACKLIGHT_TOGGLE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
-                "KEYBOARD_BACKLIGHT_TOGGLE"),
-        VOLUME_UP(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
-                "VOLUME_UP"),
-        VOLUME_DOWN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
-                "VOLUME_DOWN"),
-        VOLUME_MUTE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
-                "VOLUME_MUTE"),
-        ALL_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
-                "ALL_APPS"),
-        LAUNCH_SEARCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
-                "LAUNCH_SEARCH"),
-        LANGUAGE_SWITCH(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
-                "LANGUAGE_SWITCH"),
-        ACCESSIBILITY_ALL_APPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
-                "ACCESSIBILITY_ALL_APPS"),
-        TOGGLE_CAPS_LOCK(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
-                "TOGGLE_CAPS_LOCK"),
-        SYSTEM_MUTE(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
-                "SYSTEM_MUTE"),
-        SPLIT_SCREEN_NAVIGATION(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
-                "SPLIT_SCREEN_NAVIGATION"),
-
-        CHANGE_SPLITSCREEN_FOCUS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
-                "CHANGE_SPLITSCREEN_FOCUS"),
-        TRIGGER_BUG_REPORT(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
-                "TRIGGER_BUG_REPORT"),
-        LOCK_SCREEN(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
-                "LOCK_SCREEN"),
-        OPEN_NOTES(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
-                "OPEN_NOTES"),
-        TOGGLE_POWER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
-                "TOGGLE_POWER"),
-        SYSTEM_NAVIGATION(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
-                "SYSTEM_NAVIGATION"),
-        SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
-                "SLEEP"),
-        WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
-                "WAKEUP"),
-        MEDIA_KEY(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
-                "MEDIA_KEY"),
-        LAUNCH_DEFAULT_BROWSER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
-                "LAUNCH_DEFAULT_BROWSER"),
-        LAUNCH_DEFAULT_EMAIL(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
-                "LAUNCH_DEFAULT_EMAIL"),
-        LAUNCH_DEFAULT_CONTACTS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
-                "LAUNCH_DEFAULT_CONTACTS"),
-        LAUNCH_DEFAULT_CALENDAR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
-                "LAUNCH_DEFAULT_CALENDAR"),
-        LAUNCH_DEFAULT_CALCULATOR(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
-                "LAUNCH_DEFAULT_CALCULATOR"),
-        LAUNCH_DEFAULT_MUSIC(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
-                "LAUNCH_DEFAULT_MUSIC"),
-        LAUNCH_DEFAULT_MAPS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
-                "LAUNCH_DEFAULT_MAPS"),
-        LAUNCH_DEFAULT_MESSAGING(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
-                "LAUNCH_DEFAULT_MESSAGING"),
-        LAUNCH_DEFAULT_GALLERY(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
-                "LAUNCH_DEFAULT_GALLERY"),
-        LAUNCH_DEFAULT_FILES(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
-                "LAUNCH_DEFAULT_FILES"),
-        LAUNCH_DEFAULT_WEATHER(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
-                "LAUNCH_DEFAULT_WEATHER"),
-        LAUNCH_DEFAULT_FITNESS(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
-                "LAUNCH_DEFAULT_FITNESS"),
-        LAUNCH_APPLICATION_BY_PACKAGE_NAME(
-                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
-                "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
-        DESKTOP_MODE(
-                FrameworkStatsLog
-                        .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
-                "DESKTOP_MODE"),
-        MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
-                .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
-                "MULTIWINDOW_NAVIGATION");
-
-
-        private final int mValue;
-        private final String mName;
-
-        private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
-
-        static {
-            for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
-                VALUE_TO_ENUM_MAP.put(type.mValue, type);
-            }
-        }
-
-        KeyboardLogEvent(int enumValue, String enumName) {
-            mValue = enumValue;
-            mName = enumName;
-        }
-
-        public int getIntValue() {
-            return mValue;
-        }
-
-        /**
-         * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
-         * value will return {@code null}
-         */
-        @Nullable
-        public static KeyboardLogEvent from(int value) {
-            return VALUE_TO_ENUM_MAP.get(value);
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
-         */
-        @Nullable
-        public static KeyboardLogEvent getVolumeEvent(int keycode) {
-            switch (keycode) {
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    return VOLUME_DOWN;
-                case KeyEvent.KEYCODE_VOLUME_UP:
-                    return VOLUME_UP;
-                case KeyEvent.KEYCODE_VOLUME_MUTE:
-                    return VOLUME_MUTE;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to brightness up/down key events.
-         */
-        @Nullable
-        public static KeyboardLogEvent getBrightnessEvent(int keycode) {
-            switch (keycode) {
-                case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
-                    return BRIGHTNESS_DOWN;
-                case KeyEvent.KEYCODE_BRIGHTNESS_UP:
-                    return BRIGHTNESS_UP;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to intent filter category. Returns
-         * {@code null if no matching event found}
-         */
-        @Nullable
-        public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
-            Intent selectorIntent = intent.getSelector();
-            if (selectorIntent != null) {
-                Set<String> selectorCategories = selectorIntent.getCategories();
-                if (selectorCategories != null && !selectorCategories.isEmpty()) {
-                    for (String intentCategory : selectorCategories) {
-                        KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
-                        if (logEvent == null) {
-                            continue;
-                        }
-                        return logEvent;
-                    }
-                }
-            }
-
-            // The shortcut may be targeting a system role rather than using an intent selector,
-            // so check for that.
-            String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
-            if (!TextUtils.isEmpty(role)) {
-                return getLogEventFromRole(role);
-            }
-
-            Set<String> intentCategories = intent.getCategories();
-            if (intentCategories == null || intentCategories.isEmpty()
-                    || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
-                return null;
-            }
-            if (intent.getComponent() == null) {
-                return null;
-            }
-
-            // TODO(b/280423320): Add new field package name associated in the
-            //  KeyboardShortcutEvent atom and log it accordingly.
-            return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
-        }
-
-        @Nullable
-        private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
-            switch (category) {
-                case Intent.CATEGORY_APP_BROWSER:
-                    return LAUNCH_DEFAULT_BROWSER;
-                case Intent.CATEGORY_APP_EMAIL:
-                    return LAUNCH_DEFAULT_EMAIL;
-                case Intent.CATEGORY_APP_CONTACTS:
-                    return LAUNCH_DEFAULT_CONTACTS;
-                case Intent.CATEGORY_APP_CALENDAR:
-                    return LAUNCH_DEFAULT_CALENDAR;
-                case Intent.CATEGORY_APP_CALCULATOR:
-                    return LAUNCH_DEFAULT_CALCULATOR;
-                case Intent.CATEGORY_APP_MUSIC:
-                    return LAUNCH_DEFAULT_MUSIC;
-                case Intent.CATEGORY_APP_MAPS:
-                    return LAUNCH_DEFAULT_MAPS;
-                case Intent.CATEGORY_APP_MESSAGING:
-                    return LAUNCH_DEFAULT_MESSAGING;
-                case Intent.CATEGORY_APP_GALLERY:
-                    return LAUNCH_DEFAULT_GALLERY;
-                case Intent.CATEGORY_APP_FILES:
-                    return LAUNCH_DEFAULT_FILES;
-                case Intent.CATEGORY_APP_WEATHER:
-                    return LAUNCH_DEFAULT_WEATHER;
-                case Intent.CATEGORY_APP_FITNESS:
-                    return LAUNCH_DEFAULT_FITNESS;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Find KeyboardLogEvent corresponding to the provide system role name.
-         * Returns {@code null} if no matching event found.
-         */
-        @Nullable
-        private static KeyboardLogEvent getLogEventFromRole(String role) {
-            if (RoleManager.ROLE_BROWSER.equals(role)) {
-                return LAUNCH_DEFAULT_BROWSER;
-            } else if (RoleManager.ROLE_SMS.equals(role)) {
-                return LAUNCH_DEFAULT_MESSAGING;
-            } else {
-                Log.w(TAG, "Keyboard shortcut to launch "
-                        + role + " not supported for logging");
-                return null;
-            }
-        }
-    }
-
     /**
      * Log keyboard system shortcuts for the proto
      * {@link com.android.os.input.KeyboardSystemsEventReported}
      * defined in "stats/atoms/input/input_extension_atoms.proto"
      */
-    public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
-            @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
-        // Logging Keyboard system event only for an external HW keyboard. We should not log events
-        // for virtual keyboards or internal Key events.
-        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
-            return;
-        }
-        if (keyboardSystemEvent == null) {
-            Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
-                    + ", modifier state = " + modifierState);
-            return;
-        }
+    public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice,
+            @NonNull KeyboardSystemShortcut keyboardSystemShortcut) {
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                 inputDevice.getVendorId(), inputDevice.getProductId(),
-                keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
-                inputDevice.getDeviceBus());
+                keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(),
+                keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus());
 
         if (DEBUG) {
-            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
+            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
new file mode 100644
index 0000000..092058e
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.BinderThread;
+import android.hardware.input.IKeyboardSystemShortcutListener;
+import android.hardware.input.KeyboardSystemShortcut;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
+ * keyboard shortcut is triggered.
+ */
+final class KeyboardShortcutCallbackHandler {
+
+    private static final String TAG = "KeyboardShortcut";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // List of currently registered keyboard system shortcut listeners keyed by process pid
+    @GuardedBy("mKeyboardSystemShortcutListenerRecords")
+    private final SparseArray<KeyboardSystemShortcutListenerRecord>
+            mKeyboardSystemShortcutListenerRecords = new SparseArray<>();
+
+    public void onKeyboardSystemShortcutTriggered(int deviceId,
+            KeyboardSystemShortcut systemShortcut) {
+        if (DEBUG) {
+            Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId
+                    + ", systemShortcut = " + systemShortcut);
+        }
+
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) {
+                mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered(
+                        deviceId, systemShortcut);
+            }
+        }
+    }
+
+    /** Register the keyboard system shortcut listener for a process. */
+    @BinderThread
+    public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+            int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) {
+                throw new IllegalStateException("The calling process has already registered "
+                        + "a KeyboardSystemShortcutListener.");
+            }
+            KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord(
+                    pid, listener);
+            try {
+                listener.asBinder().linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(ex);
+            }
+            mKeyboardSystemShortcutListenerRecords.put(pid, record);
+        }
+    }
+
+    /** Unregister the keyboard system shortcut listener for a process. */
+    @BinderThread
+    public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+            int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            KeyboardSystemShortcutListenerRecord record =
+                    mKeyboardSystemShortcutListenerRecords.get(pid);
+            if (record == null) {
+                throw new IllegalStateException("The calling process has no registered "
+                        + "KeyboardSystemShortcutListener.");
+            }
+            if (record.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalStateException("The calling process has a different registered "
+                        + "KeyboardSystemShortcutListener.");
+            }
+            record.mListener.asBinder().unlinkToDeath(record, 0);
+            mKeyboardSystemShortcutListenerRecords.remove(pid);
+        }
+    }
+
+    private void onKeyboardSystemShortcutListenerDied(int pid) {
+        synchronized (mKeyboardSystemShortcutListenerRecords) {
+            mKeyboardSystemShortcutListenerRecords.remove(pid);
+        }
+    }
+
+    // A record of a registered keyboard system shortcut listener from one process.
+    private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient {
+        public final int mPid;
+        public final IKeyboardSystemShortcutListener mListener;
+
+        KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died.");
+            }
+            onKeyboardSystemShortcutListenerDied(mPid);
+        }
+
+        public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) {
+            try {
+                mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(),
+                        data.getModifierState(), data.getSystemShortcut());
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid
+                        + " that keyboard system shortcut was triggered, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 94b1473..079b724 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -109,10 +109,6 @@
      * <dd>
      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
      * </dd>
-     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
-     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
-     *    currently invisible.
-     * </dd>
      * </dl>
      * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
      * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4d06f50..e36d5bb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,7 @@
 
 package com.android.server.inputmethod;
 
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -140,23 +140,26 @@
      * to be switched.
      */
     public boolean switchToInputMethod(@NonNull String imeId, @UserIdInt int userId) {
-        return switchToInputMethod(imeId, NOT_A_SUBTYPE_ID, userId);
+        return switchToInputMethod(imeId, NOT_A_SUBTYPE_INDEX, userId);
     }
 
     /**
      * Force switch to the enabled input method by {@code imeId} for the current user. If the input
-     * method with {@code imeId} is not enabled or not installed, do nothing. If {@code subtypeId}
-     * is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_ID}) and valid, also switches to
-     * it, otherwise the system decides the most sensible default subtype to use.
+     * method with {@code imeId} is not enabled or not installed, do nothing. If
+     * {@code subtypeIndex} is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}) and
+     * valid, also switches to it, otherwise the system decides the most sensible default subtype to
+     * use.
      *
-     * @param imeId the input method ID to be switched to
-     * @param subtypeId the input method subtype ID to be switched to
-     * @param userId the user ID to be queried
+     * @param imeId        the input method ID to be switched to
+     * @param subtypeIndex the subtype to be switched to, as an index in the input method's array of
+     *                     subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if the system
+     *                     should decide the most sensible subtype
+     * @param userId       the user ID to be queried
      * @return {@code true} if the current input method was successfully switched to the input
      * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
      * to be switched.
      */
-    public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+    public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
             @UserIdInt int userId);
 
     /**
@@ -376,7 +379,7 @@
                 }
 
                 @Override
-                public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+                public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
                         @UserIdInt int userId) {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ea3d7d1..3863cf0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,10 +49,12 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -81,7 +83,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.input.InputManager;
 import android.inputmethodservice.InputMethodService;
@@ -273,11 +274,6 @@
 
     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
-
-    private static final int INVALID_SUBTYPE_HASHCODE =
-            InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
-
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
     private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
@@ -303,28 +299,6 @@
     private final String[] mNonPreemptibleInputMethods;
 
     /**
-     * Whether the new Input Method Switcher menu is enabled.
-     *
-     * @see #shouldEnableNewInputMethodSwitcherMenu
-     */
-    @SharedByAllUsersField
-    private final boolean mNewInputMethodSwitcherMenuEnabled;
-
-    /**
-     * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
-     * {@code false} for watches and small screen devices.
-     *
-     * @param context the context to check the device configuration for.
-     */
-    private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
-        final boolean isWatch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_WATCH);
-        final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
-        return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
-    }
-
-    /**
      * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
      */
     @SharedByAllUsersField
@@ -612,7 +586,7 @@
     private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
         switch (key) {
             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
-                if (!mNewInputMethodSwitcherMenuEnabled) {
+                if (!Flags.imeSwitcherRevamp()) {
                     if (userId == mCurrentUserId) {
                         mMenuController.updateKeyboardFromSettingsLocked(userId);
                     }
@@ -680,7 +654,7 @@
                         return;
                     }
                     final int userId = mCurrentUserId;
-                    if (mNewInputMethodSwitcherMenuEnabled) {
+                    if (Flags.imeSwitcherRevamp()) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
                     } else {
@@ -718,7 +692,7 @@
                     userData.mRawInputMethodMap.set(rawMethodMap);
                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
                             DirectBootAwareness.AUTO,
-                            mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            userData.mIsUnlockingOrUnlocked.get());
                     final var settings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, settings);
                 }
@@ -842,7 +816,7 @@
             final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                     newAdditionalSubtypeMap,
                     DirectBootAwareness.AUTO,
-                    mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                    userData.mIsUnlockingOrUnlocked.get());
 
             final boolean noUpdate = InputMethodMap.areSame(settings.getMethodMap(), newMethodMap);
             if (noUpdate && imesToBeDisabled.isEmpty()) {
@@ -1072,6 +1046,7 @@
             final int userId = user.getUserIdentifier();
             final var userData = mService.getUserData(userId);
             final boolean userUnlocked = true;
+            userData.mIsUnlockingOrUnlocked.set(userUnlocked);
             SecureSettingsWrapper.onUserUnlocking(userId);
             final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                     AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
@@ -1122,9 +1097,12 @@
                     final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
                     final var rawMethodMap = queryRawInputMethodServiceMap(context, userId);
                     userData.mRawInputMethodMap.set(rawMethodMap);
+
+                    final boolean unlocked = userManagerInternal.isUserUnlockingOrUnlocked(userId);
+                    userData.mIsUnlockingOrUnlocked.set(unlocked);
                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
-                            DirectBootAwareness.AUTO,
-                            userManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            DirectBootAwareness.AUTO, unlocked);
+
                     final var settings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, settings);
 
@@ -1152,6 +1130,7 @@
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
             final var rawMethodMap = userData.mRawInputMethodMap.get();
             final boolean userUnlocked = false;  // Stopping a user also locks their storage.
+            userData.mIsUnlockingOrUnlocked.set(userUnlocked);
             final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
                     DirectBootAwareness.AUTO, userUnlocked);
             InputMethodSettingsRepository.put(userId,
@@ -1205,7 +1184,6 @@
             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
             mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
-            mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
 
             mShowOngoingImeSwitcherForPhones = false;
 
@@ -1221,7 +1199,7 @@
                             : bindingControllerFactory, visibilityStateComputerFactory);
 
             mMenuController = new InputMethodMenuController(this);
-            mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
+            mMenuControllerNew = Flags.imeSwitcherRevamp()
                     ? new InputMethodMenuControllerNew() : null;
             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
 
@@ -1289,7 +1267,7 @@
         if (DEBUG) {
             Slog.i(TAG, "Default found, using " + defIm.getId());
         }
-        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
+        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_INDEX, false, userId);
     }
 
     @NonNull
@@ -1355,6 +1333,17 @@
         }
         updateFromSettingsLocked(true, newUserId);
 
+        // Special workaround for b/356879517.
+        // KeyboardLayoutManager still expects onInputMethodSubtypeChangedForKeyboardLayoutMapping
+        // to be called back upon IME user switching, while we are actively deprecating the concept
+        // of "current IME user" at b/350386877.
+        // TODO(b/356879517): Come up with a way to avoid this special handling.
+        if (newUserData.mSubtypeForKeyboardLayoutMapping != null) {
+            final var subtypeHandleAndSubtype = newUserData.mSubtypeForKeyboardLayoutMapping;
+            mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+                    newUserId, subtypeHandleAndSubtype.first, subtypeHandleAndSubtype.second);
+        }
+
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
@@ -1635,7 +1624,7 @@
         final var userData = getUserData(userId);
         final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                 AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
-                mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                userData.mIsUnlockingOrUnlocked.get());
         final var settings = InputMethodSettings.create(methodMap, userId);
         // Create a copy.
         final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
@@ -1810,7 +1799,7 @@
                     ImeTracker.PHASE_SERVER_WAIT_IME);
             userData.mCurStatsToken = null;
             // TODO: Make mMenuController multi-user aware
-            if (mNewInputMethodSwitcherMenuEnabled) {
+            if (Flags.imeSwitcherRevamp()) {
                 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
             } else {
                 mMenuController.hideInputMethodMenuLocked(userId);
@@ -2018,7 +2007,7 @@
         if (deviceMethodId == null) {
             visibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
         } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
-            setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID,
+            setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_INDEX,
                     bindingController.getDeviceIdToShowIme(), userId);
             selectedMethodId = deviceMethodId;
         }
@@ -2620,7 +2609,7 @@
         if (!mShowOngoingImeSwitcherForPhones) return false;
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         // TODO(b/305849394): Make mMenuController multi-user aware.
-        final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+        final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
                 ? mMenuControllerNew.isShowing()
                 : mMenuController.getSwitchingDialogLocked() != null;
         if (switcherMenuShowing) {
@@ -2636,12 +2625,10 @@
                 && mWindowManagerInternal.isKeyguardSecure(userId)) {
             return false;
         }
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0
-                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0) {
             return false;
         }
-        if (mWindowManagerInternal.isHardKeyboardAvailable()
-                && !mNewInputMethodSwitcherMenuEnabled) {
+        if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
             // When physical keyboard is attached, we show the ime switcher (or notification if
             // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
             // exists in the IME switcher dialog.  Might be OK to remove this condition once
@@ -2652,7 +2639,7 @@
         }
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-        if (mNewInputMethodSwitcherMenuEnabled) {
+        if (Flags.imeSwitcherRevamp()) {
             // The IME switcher button should be shown when the current IME specified a
             // language settings activity.
             final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2803,7 +2790,7 @@
         if (DEBUG) {
             Slog.d(TAG, "IME window vis: " + vis
                     + " active: " + (vis & InputMethodService.IME_ACTIVE)
-                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+                    + " visible: " + (vis & InputMethodService.IME_VISIBLE)
                     + " displayId: " + curTokenDisplayId);
         }
         final IBinder focusedWindowToken = userData.mImeBindingState != null
@@ -2825,7 +2812,7 @@
             }
             final var curId = bindingController.getCurId();
             // TODO(b/305849394): Make mMenuController multi-user aware.
-            final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+            final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.getSwitchingDialogLocked() != null;
             if (switcherMenuShowing
@@ -2847,7 +2834,7 @@
     @GuardedBy("ImfLock.class")
     void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
         updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
-        if (!mNewInputMethodSwitcherMenuEnabled) {
+        if (!Flags.imeSwitcherRevamp()) {
             mMenuController.updateKeyboardFromSettingsLocked(userId);
         }
     }
@@ -2915,7 +2902,7 @@
         }
         if (!TextUtils.isEmpty(id)) {
             try {
-                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id), userId);
+                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeIndex(id), userId);
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId);
@@ -2938,17 +2925,28 @@
                         ? subtype : null;
         final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
                 ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+
+        final var userData = getUserData(userId);
+
+        // A workaround for b/356879517. KeyboardLayoutManager has relied on an implementation
+        // detail that IMMS triggers this callback only for the current IME user.
+        // TODO(b/357663774): Figure out how to better handle this scenario.
+        userData.mSubtypeForKeyboardLayoutMapping =
+                Pair.create(newSubtypeHandle, normalizedSubtype);
+        if (userId != mCurrentUserId) {
+            return;
+        }
         mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
                 userId, newSubtypeHandle, normalizedSubtype);
     }
 
     @GuardedBy("ImfLock.class")
-    void setInputMethodLocked(String id, int subtypeId, @UserIdInt int userId) {
-        setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT, userId);
+    void setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId) {
+        setInputMethodLocked(id, subtypeIndex, DEVICE_ID_DEFAULT, userId);
     }
 
     @GuardedBy("ImfLock.class")
-    void setInputMethodLocked(String id, int subtypeId, int deviceId, @UserIdInt int userId) {
+    void setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         InputMethodInfo info = settings.getMethodMap().get(id);
         if (info == null) {
@@ -2965,25 +2963,25 @@
             }
             final InputMethodSubtype oldSubtype = bindingController.getCurrentSubtype();
             final InputMethodSubtype newSubtype;
-            if (subtypeId >= 0 && subtypeId < subtypeCount) {
-                newSubtype = info.getSubtypeAt(subtypeId);
+            if (subtypeIndex >= 0 && subtypeIndex < subtypeCount) {
+                newSubtype = info.getSubtypeAt(subtypeIndex);
             } else {
                 // If subtype is null, try to find the most applicable one from
                 // getCurrentInputMethodSubtype.
-                subtypeId = NOT_A_SUBTYPE_ID;
+                subtypeIndex = NOT_A_SUBTYPE_INDEX;
                 // TODO(b/347083680): The method below has questionable behaviors.
                 newSubtype = bindingController.getCurrentInputMethodSubtype();
                 if (newSubtype != null) {
                     for (int i = 0; i < subtypeCount; ++i) {
                         if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
-                            subtypeId = i;
+                            subtypeIndex = i;
                             break;
                         }
                     }
                 }
             }
             if (!Objects.equals(newSubtype, oldSubtype)) {
-                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
+                setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, true, userId);
                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
                 if (curMethod != null) {
                     updateSystemUiLocked(bindingController.getImeWindowVis(),
@@ -3010,9 +3008,7 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            // Set a subtype to this input method.
-            // subtypeId the name of a subtype which will be set.
-            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false, userId);
+            setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, false, userId);
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
@@ -4016,7 +4012,7 @@
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
         synchronized (ImfLock.class) {
-            return mNewInputMethodSwitcherMenuEnabled
+            return Flags.imeSwitcherRevamp()
                     ? mMenuControllerNew.isShowing()
                     : mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4025,11 +4021,12 @@
     /**
      * Gets the list of Input Method Switcher Menu items and the index of the selected item.
      *
-     * @param items             the list of input method and subtype items.
-     * @param selectedImeId     the ID of the selected input method.
-     * @param selectedSubtypeId the ID of the selected input method subtype,
-     *                          or {@link #NOT_A_SUBTYPE_ID} if no subtype is selected.
-     * @param userId            the ID of the user for which to get the menu items.
+     * @param items                the list of input method and subtype items.
+     * @param selectedImeId        the ID of the selected input method.
+     * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
+     *                             subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
+     *                             subtype is selected.
+     * @param userId               the ID of the user for which to get the menu items.
      * @return the list of menu items, and the index of the selected item,
      * or {@code -1} if no item is selected.
      */
@@ -4037,17 +4034,17 @@
     @NonNull
     private Pair<List<MenuItem>, Integer> getInputMethodPickerItems(
             @NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
-            int selectedSubtypeId, @UserIdInt int userId) {
+            int selectedSubtypeIndex, @UserIdInt int userId) {
         final var bindingController = getInputMethodBindingController(userId);
         final var settings = InputMethodSettingsRepository.get(userId);
 
-        if (selectedSubtypeId == NOT_A_SUBTYPE_ID) {
+        if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
             // TODO(b/351124299): Check if this fallback logic is still necessary.
             final var curSubtype = bindingController.getCurrentInputMethodSubtype();
             if (curSubtype != null) {
                 final var curMethodId = bindingController.getSelectedMethodId();
                 final var curImi = settings.getMethodMap().get(curMethodId);
-                selectedSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
                         curImi, curSubtype.hashCode());
             }
         }
@@ -4062,19 +4059,19 @@
             final var item = items.get(i);
             final var imeId = item.mImi.getId();
             if (imeId.equals(selectedImeId)) {
-                final int subtypeId = item.mSubtypeId;
+                final int subtypeIndex = item.mSubtypeIndex;
                 // Check if this is the selected IME-subtype pair.
-                if ((subtypeId == 0 && selectedSubtypeId == NOT_A_SUBTYPE_ID)
-                        || subtypeId == NOT_A_SUBTYPE_ID
-                        || subtypeId == selectedSubtypeId) {
+                if ((subtypeIndex == 0 && selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX)
+                        || subtypeIndex == NOT_A_SUBTYPE_INDEX
+                        || subtypeIndex == selectedSubtypeIndex) {
                     selectedIndex = i;
                 }
             }
             final boolean hasHeader = !imeId.equals(prevImeId);
             final boolean hasDivider = hasHeader && prevImeId != null;
             prevImeId = imeId;
-            menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi, item.mSubtypeId,
-                    hasHeader, hasDivider));
+            menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi,
+                    item.mSubtypeIndex, hasHeader, hasDivider));
         }
 
         return new Pair<>(menuItems, selectedIndex);
@@ -4131,10 +4128,10 @@
                 imi.getPackageName(), callingUid, userId, settings)) {
             throw getExceptionForUnknownImeId(id);
         }
-        final int subtypeId = subtype != null
-                ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
-                : NOT_A_SUBTYPE_ID;
-        setInputMethodWithSubtypeIdLocked(id, subtypeId, userId);
+        final int subtypeIndex = subtype != null
+                ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+                : NOT_A_SUBTYPE_INDEX;
+        setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId);
     }
 
     @BinderThread
@@ -4152,18 +4149,18 @@
         }
         final var currentSubtype = bindingController.getCurrentSubtype();
         String targetLastImiId = null;
-        int subtypeId = NOT_A_SUBTYPE_ID;
+        int subtypeIndex = NOT_A_SUBTYPE_INDEX;
         if (lastIme != null && lastImi != null) {
             final boolean imiIdIsSame = lastImi.getId().equals(
                     bindingController.getSelectedMethodId());
             final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-            final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_ID
+            final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_INDEX
                     : currentSubtype.hashCode();
             // If the last IME is the same as the current IME and the last subtype is not
             // defined, there is no need to switch to the last IME.
             if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
                 targetLastImiId = lastIme.first;
-                subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+                subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash);
             }
         }
 
@@ -4191,7 +4188,7 @@
                                         SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
                         if (keyboardSubtype != null) {
                             targetLastImiId = imi.getId();
-                            subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+                            subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
                                     keyboardSubtype.hashCode());
                             if (keyboardSubtype.getLocale().equals(locale)) {
                                 break;
@@ -4206,9 +4203,9 @@
             if (DEBUG) {
                 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
                         + ", from: " + bindingController.getSelectedMethodId() + ", "
-                        + subtypeId);
+                        + subtypeIndex);
             }
-            setInputMethodWithSubtypeIdLocked(targetLastImiId, subtypeId, userId);
+            setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId);
             return true;
         } else {
             return false;
@@ -4228,7 +4225,7 @@
         if (nextSubtype == null) {
             return false;
         }
-        setInputMethodWithSubtypeIdLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId,
+        setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex,
                 userData.mUserId);
         return true;
     }
@@ -4294,7 +4291,7 @@
                 try {
                     final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
                             AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
-                            mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+                            userData.mIsUnlockingOrUnlocked.get());
                     final var newSettings = InputMethodSettings.create(methodMap, userId);
                     InputMethodSettingsRepository.put(userId, newSettings);
                     postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
@@ -4661,7 +4658,7 @@
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
-            if (!mNewInputMethodSwitcherMenuEnabled) {
+            if (!Flags.imeSwitcherRevamp()) {
                 proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
                         mMenuController.getShowImeWithHardKeyboard());
             }
@@ -4715,7 +4712,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void setInputMethodWithSubtypeIdLocked(String id, int subtypeId,
+    private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex,
             @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         if (settings.getMethodMap().get(id) != null
@@ -4725,7 +4722,7 @@
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            setInputMethodLocked(id, subtypeId, userId);
+            setInputMethodLocked(id, subtypeIndex, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -4886,7 +4883,8 @@
         final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
                 && mWindowManagerInternal.isKeyguardSecure(userId);
         final String lastInputMethodId = settings.getSelectedInputMethod();
-        int lastInputMethodSubtypeId = settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+        final int lastInputMethodSubtypeIndex =
+                settings.getSelectedInputMethodSubtypeIndex(lastInputMethodId);
 
         final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                 .getSortedInputMethodAndSubtypeList(
@@ -4900,30 +4898,30 @@
             return;
         }
 
-        if (mNewInputMethodSwitcherMenuEnabled) {
+        if (Flags.imeSwitcherRevamp()) {
             if (DEBUG) {
                 Slog.v(TAG, "Show IME switcher menu,"
                         + " showAuxSubtypes=" + showAuxSubtypes
                         + " displayId=" + displayId
                         + " preferredInputMethodId=" + lastInputMethodId
-                        + " preferredInputMethodSubtypeId=" + lastInputMethodSubtypeId);
+                        + " preferredInputMethodSubtypeIndex=" + lastInputMethodSubtypeIndex);
             }
 
             final var itemsAndIndex = getInputMethodPickerItems(imList,
-                    lastInputMethodId, lastInputMethodSubtypeId, userId);
+                    lastInputMethodId, lastInputMethodSubtypeIndex, userId);
             final var menuItems = itemsAndIndex.first;
             final int selectedIndex = itemsAndIndex.second;
 
             if (selectedIndex == -1) {
                 Slog.w(TAG, "Switching menu shown with no item selected"
                         + ", IME id: " + lastInputMethodId
-                        + ", subtype index: " + lastInputMethodSubtypeId);
+                        + ", subtype index: " + lastInputMethodSubtypeIndex);
             }
 
             mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
         } else {
             mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
-                    lastInputMethodId, lastInputMethodSubtypeId, imList, userId);
+                    lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId);
         }
     }
 
@@ -4990,7 +4988,7 @@
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
-                if (!mNewInputMethodSwitcherMenuEnabled) {
+                if (!Flags.imeSwitcherRevamp()) {
                     mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
                 }
                 synchronized (ImfLock.class) {
@@ -5442,7 +5440,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
+    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex,
             boolean setSubtypeOnly, @UserIdInt int userId) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final var bindingController = getInputMethodBindingController(userId);
@@ -5452,12 +5450,12 @@
         // Set Subtype here
         final int newSubtypeHashcode;
         final InputMethodSubtype newSubtype;
-        if (imi == null || subtypeId < 0) {
+        if (imi == null || subtypeIndex < 0) {
             newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
             newSubtype = null;
         } else {
-            if (subtypeId < imi.getSubtypeCount()) {
-                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
+            if (subtypeIndex < imi.getSubtypeCount()) {
+                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
                 newSubtypeHashcode = subtype.hashCode();
                 newSubtype = subtype;
             } else {
@@ -5493,20 +5491,20 @@
         settings.putSelectedDefaultDeviceInputMethod(null);
 
         InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
-        int lastSubtypeId = NOT_A_SUBTYPE_ID;
+        int lastSubtypeIndex = NOT_A_SUBTYPE_INDEX;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
             String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
-                    lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+                    lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
                             Integer.parseInt(subtypeHashCode));
                 } catch (NumberFormatException e) {
                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
                 }
             }
         }
-        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false, userId);
+        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeIndex, false, userId);
     }
 
     /**
@@ -5530,14 +5528,14 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
+    private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeIndex,
             @UserIdInt int userId) {
         final var settings = InputMethodSettingsRepository.get(userId);
         final var enabledImes = settings.getEnabledInputMethodList();
         if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
             return false; // IME is not found or not enabled.
         }
-        setInputMethodLocked(imeId, subtypeId, userId);
+        setInputMethodLocked(imeId, subtypeIndex, userId);
         return true;
     }
 
@@ -5593,8 +5591,8 @@
                 return;
             }
 
-            final var nextSubtype = nextItem.mSubtypeId > NOT_A_SUBTYPE_ID
-                    ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeId) : null;
+            final var nextSubtype = nextItem.mSubtypeIndex > NOT_A_SUBTYPE_INDEX
+                    ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeIndex) : null;
             nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
         } else {
             final InputMethodSubtypeHandle currentSubtypeHandle =
@@ -5613,7 +5611,7 @@
         final int subtypeCount = nextImi.getSubtypeCount();
         if (subtypeCount == 0) {
             if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
-                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID, userId);
+                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_INDEX, userId);
             }
             return;
         }
@@ -5691,10 +5689,10 @@
         }
 
         @Override
-        public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+        public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                return switchToInputMethodLocked(imeId, subtypeId, userId);
+                return switchToInputMethodLocked(imeId, subtypeIndex, userId);
             }
         }
 
@@ -5776,7 +5774,7 @@
                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
                 if (visibilityStateComputer.getLastImeTargetWindow()
                         != userData.mImeBindingState.mFocusedWindow) {
-                    if (mNewInputMethodSwitcherMenuEnabled) {
+                    if (Flags.imeSwitcherRevamp()) {
                         final var bindingController = getInputMethodBindingController(userId);
                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
                     } else {
@@ -6111,6 +6109,7 @@
             @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
                     u -> {
                         p.println("    mUserId=" + u.mUserId);
+                        p.println("      unlocked=" + u.mIsUnlockingOrUnlocked.get());
                         p.println("      hasMainConnection="
                                 + u.mBindingController.hasMainConnection());
                         p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
@@ -6134,7 +6133,7 @@
                     };
             mUserDataRepository.forAllUserData(userDataDump);
 
-            if (mNewInputMethodSwitcherMenuEnabled) {
+            if (Flags.imeSwitcherRevamp()) {
                 p.println("  menuControllerNew:");
                 mMenuControllerNew.dump(p, "  ");
             } else {
@@ -6563,7 +6562,7 @@
                         continue;
                     }
                     boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
-                            NOT_A_SUBTYPE_ID, userId);
+                            NOT_A_SUBTYPE_INDEX, userId);
                     if (failedToSelectUnknownIme) {
                         error.print("Unknown input method ");
                         error.print(imeId);
@@ -6577,17 +6576,6 @@
                         out.print(imeId);
                         out.print(" selected for user #");
                         out.println(userId);
-
-                        // Workaround for b/354782333.
-                        final var settingsValue = SecureSettingsWrapper.getString(
-                                Settings.Secure.DEFAULT_INPUT_METHOD, "", userId);
-                        if (!TextUtils.equals(settingsValue, imeId)) {
-                            Slog.w(TAG, "DEFAULT_INPUT_METHOD=" + settingsValue
-                                    + " is not updated. Fixing it up to " + imeId
-                                    + " See b/354782333.");
-                            SecureSettingsWrapper.putString(
-                                    Settings.Secure.DEFAULT_INPUT_METHOD, imeId, userId);
-                        }
                     }
                     hasFailed |= failedToSelectUnknownIme;
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index f16a5a0..b5ee068 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -17,7 +17,7 @@
 package com.android.server.inputmethod;
 
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -62,7 +62,7 @@
     private View mSwitchingDialogTitleView;
     private List<ImeSubtypeListItem> mImList;
     private InputMethodInfo[] mIms;
-    private int[] mSubtypeIds;
+    private int[] mSubtypeIndices;
 
     private boolean mShowImeWithHardKeyboard;
 
@@ -77,7 +77,7 @@
 
     @GuardedBy("ImfLock.class")
     void showInputMethodMenuLocked(boolean showAuxSubtypes, int displayId,
-            String preferredInputMethodId, int preferredInputMethodSubtypeId,
+            String preferredInputMethodId, int preferredInputMethodSubtypeIndex,
             @NonNull List<ImeSubtypeListItem> imList, @UserIdInt int userId) {
         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
 
@@ -85,14 +85,14 @@
 
         hideInputMethodMenuLocked(userId);
 
-        if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+        if (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
             final InputMethodSubtype currentSubtype =
                     bindingController.getCurrentInputMethodSubtype();
             if (currentSubtype != null) {
                 final String curMethodId = bindingController.getSelectedMethodId();
                 final InputMethodInfo currentImi =
                         InputMethodSettingsRepository.get(userId).getMethodMap().get(curMethodId);
-                preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                preferredInputMethodSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
                         currentImi, currentSubtype.hashCode());
             }
         }
@@ -101,7 +101,7 @@
         final int size = imList.size();
         mImList = imList;
         mIms = new InputMethodInfo[size];
-        mSubtypeIds = new int[size];
+        mSubtypeIndices = new int[size];
         // No items are checked by default. When we have a list of explicitly enabled subtypes,
         // the implicit subtype is no longer listed, but if it is still the selected one,
         // no items will be shown as checked.
@@ -109,12 +109,13 @@
         for (int i = 0; i < size; ++i) {
             final ImeSubtypeListItem item = imList.get(i);
             mIms[i] = item.mImi;
-            mSubtypeIds[i] = item.mSubtypeId;
+            mSubtypeIndices[i] = item.mSubtypeIndex;
             if (mIms[i].getId().equals(preferredInputMethodId)) {
-                int subtypeId = mSubtypeIds[i];
-                if ((subtypeId == NOT_A_SUBTYPE_ID)
-                        || (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
-                        || (subtypeId == preferredInputMethodSubtypeId)) {
+                int subtypeIndex = mSubtypeIndices[i];
+                if ((subtypeIndex == NOT_A_SUBTYPE_INDEX)
+                        || (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX
+                        && subtypeIndex == 0)
+                        || (subtypeIndex == preferredInputMethodSubtypeIndex)) {
                     checkedItem = i;
                 }
             }
@@ -123,7 +124,7 @@
         if (checkedItem == -1) {
             Slog.w(TAG, "Switching menu shown with no item selected"
                     + ", IME id: " + preferredInputMethodId
-                    + ", subtype index: " + preferredInputMethodSubtypeId);
+                    + ", subtype index: " + preferredInputMethodSubtypeIndex);
         }
 
         if (mDialogWindowContext == null) {
@@ -171,19 +172,19 @@
                 com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
         final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
             synchronized (ImfLock.class) {
-                if (mIms == null || mIms.length <= which || mSubtypeIds == null
-                        || mSubtypeIds.length <= which) {
+                if (mIms == null || mIms.length <= which || mSubtypeIndices == null
+                        || mSubtypeIndices.length <= which) {
                     return;
                 }
                 final InputMethodInfo im = mIms[which];
-                int subtypeId = mSubtypeIds[which];
+                int subtypeIndex = mSubtypeIndices[which];
                 adapter.mCheckedItem = which;
                 adapter.notifyDataSetChanged();
                 if (im != null) {
-                    if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
-                        subtypeId = NOT_A_SUBTYPE_ID;
+                    if (subtypeIndex < 0 || subtypeIndex >= im.getSubtypeCount()) {
+                        subtypeIndex = NOT_A_SUBTYPE_INDEX;
                     }
-                    mService.setInputMethodLocked(im.getId(), subtypeId, userId);
+                    mService.setInputMethodLocked(im.getId(), subtypeIndex, userId);
                 }
                 hideInputMethodMenuLocked(userId);
             }
@@ -251,7 +252,7 @@
             mDialogBuilder = null;
             mImList = null;
             mIms = null;
-            mSubtypeIds = null;
+            mSubtypeIndices = null;
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index b72a34d..d9e9e00 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -21,7 +21,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -107,7 +107,7 @@
             if (which != selectedIndex) {
                 final var item = items.get(which);
                 InputMethodManagerInternal.get()
-                        .switchToInputMethod(item.mImi.getId(), item.mSubtypeId, userId);
+                        .switchToInputMethod(item.mImi.getId(), item.mSubtypeIndex, userId);
             }
             hide(displayId, userId);
         };
@@ -225,10 +225,10 @@
 
         /**
          * The index of the subtype in the input method's array of subtypes,
-         * or {@link InputMethodUtils#NOT_A_SUBTYPE_ID} if this item doesn't have a subtype.
+         * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
          */
-        @IntRange(from = NOT_A_SUBTYPE_ID)
-        private final int mSubtypeId;
+        @IntRange(from = NOT_A_SUBTYPE_INDEX)
+        private final int mSubtypeIndex;
 
         /** Whether this item has a group header (only the first item of each input method). */
         private final boolean mHasHeader;
@@ -240,12 +240,13 @@
         private final boolean mHasDivider;
 
         MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_ID) int subtypeId,
-                boolean hasHeader, boolean hasDivider) {
+                @NonNull InputMethodInfo imi,
+                @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex, boolean hasHeader,
+                boolean hasDivider) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
             mImi = imi;
-            mSubtypeId = subtypeId;
+            mSubtypeIndex = subtypeIndex;
             mHasHeader = hasHeader;
             mHasDivider = hasDivider;
         }
@@ -255,7 +256,7 @@
             return "MenuItem{"
                     + "mImeName=" + mImeName
                     + " mSubtypeName=" + mSubtypeName
-                    + " mSubtypeId=" + mSubtypeId
+                    + " mSubtypeIndex=" + mSubtypeIndex
                     + " mHasHeader=" + mHasHeader
                     + " mHasDivider=" + mHasDivider
                     + "}";
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 0152158..030a5fb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -54,7 +54,7 @@
     /**
      * An integer code that represents "no subtype" when a subtype hashcode is used.
      *
-     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
      * used {@code -1} here. We cannot change this value as it's already saved into secure settings.
      * </p>
      */
@@ -62,7 +62,7 @@
     /**
      * A string code that represents "no subtype" when a subtype hashcode is used.
      *
-     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+     * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
      * used {@code "-1"} here. We cannot change this value as it's already saved into secure
      * settings.</p>
      */
@@ -84,8 +84,8 @@
         // Inputmethod and subtypes are saved in the settings as follows:
         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
         for (int i = 0; i < ime.second.size(); ++i) {
-            final String subtypeId = ime.second.get(i);
-            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+            final String subtypeHashCode = ime.second.get(i);
+            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeHashCode);
         }
     }
 
@@ -350,12 +350,12 @@
         if (lastImi == null) return null;
         try {
             final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-            final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+            final int lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi,
                     lastSubtypeHash);
-            if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+            if (lastSubtypeIndex < 0 || lastSubtypeIndex >= lastImi.getSubtypeCount()) {
                 return null;
             }
-            return lastImi.getSubtypeAt(lastSubtypeId);
+            return lastImi.getSubtypeAt(lastSubtypeIndex);
         } catch (NumberFormatException e) {
             return null;
         }
@@ -427,7 +427,7 @@
                     for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) {
                         final String s = explicitlyEnabledSubtypes.get(j);
                         if (s.equals(subtypeHashCode)) {
-                            // If both imeId and subtype are enabled, return subtypeId.
+                            // If both imeId and subtype are enabled, return subtypeHashCode.
                             try {
                                 final int hashCode = Integer.parseInt(subtypeHashCode);
                                 // Check whether the subtype is valid or not
@@ -494,11 +494,11 @@
         putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
     }
 
-    void putSelectedSubtype(int subtypeId) {
+    void putSelectedSubtype(int subtypeHashCode) {
         if (DEBUG) {
-            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId);
+            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeHashCode + ", " + mUserId);
         }
-        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeHashCode);
     }
 
     @Nullable
@@ -551,13 +551,13 @@
         return mUserId;
     }
 
-    int getSelectedInputMethodSubtypeId(String selectedImiId) {
+    int getSelectedInputMethodSubtypeIndex(String selectedImiId) {
         final InputMethodInfo imi = mMethodMap.get(selectedImiId);
         if (imi == null) {
-            return InputMethodUtils.NOT_A_SUBTYPE_ID;
+            return InputMethodUtils.NOT_A_SUBTYPE_INDEX;
         }
         final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-        return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+        return SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtypeHashCode);
     }
 
     void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 05cc598..4dbbfa2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -48,7 +50,6 @@
 final class InputMethodSubtypeSwitchingController {
     private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 
     @IntDef(prefix = {"MODE_"}, value = {
             MODE_STATIC,
@@ -86,17 +87,21 @@
         public final CharSequence mSubtypeName;
         @NonNull
         public final InputMethodInfo mImi;
-        public final int mSubtypeId;
+        /**
+         * The index of the subtype in the input method's array of subtypes,
+         * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
+         */
+        public final int mSubtypeIndex;
         public final boolean mIsSystemLocale;
         public final boolean mIsSystemLanguage;
 
         ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, int subtypeId, @Nullable String subtypeLocale,
+                @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
                 @NonNull String systemLocale) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
             mImi = imi;
-            mSubtypeId = subtypeId;
+            mSubtypeIndex = subtypeIndex;
             if (TextUtils.isEmpty(subtypeLocale)) {
                 mIsSystemLocale = false;
                 mIsSystemLanguage = false;
@@ -137,7 +142,7 @@
          *   <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li>
          * </ol>
          * Note: this class has a natural ordering that is inconsistent with
-         * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeId} but
+         * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeIndex} but
          * {@link #equals(Object)} does.
          *
          * @param other the object to be compared.
@@ -177,7 +182,7 @@
             return "ImeSubtypeListItem{"
                     + "mImeName=" + mImeName
                     + " mSubtypeName=" + mSubtypeName
-                    + " mSubtypeId=" + mSubtypeId
+                    + " mSubtypeIndex=" + mSubtypeIndex
                     + " mIsSystemLocale=" + mIsSystemLocale
                     + " mIsSystemLanguage=" + mIsSystemLanguage
                     + "}";
@@ -190,7 +195,8 @@
             }
             if (o instanceof ImeSubtypeListItem) {
                 final ImeSubtypeListItem that = (ImeSubtypeListItem) o;
-                return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId;
+                return Objects.equals(this.mImi, that.mImi)
+                        && this.mSubtypeIndex == that.mSubtypeIndex;
             }
             return false;
         }
@@ -256,7 +262,7 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
                         mSystemLocaleStr));
             }
         }
@@ -310,17 +316,17 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
                         mSystemLocaleStr));
             }
         }
         return imList;
     }
 
-    private static int calculateSubtypeId(@NonNull InputMethodInfo imi,
+    private static int calculateSubtypeIndex(@NonNull InputMethodInfo imi,
             @Nullable InputMethodSubtype subtype) {
-        return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
-                : NOT_A_SUBTYPE_ID;
+        return subtype != null ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+                : NOT_A_SUBTYPE_INDEX;
     }
 
     private static class StaticRotationList {
@@ -341,12 +347,12 @@
          * @return The index in the given list. -1 if not found.
          */
         private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
-            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
             final int numSubtypes = mImeSubtypeList.size();
             for (int i = 0; i < numSubtypes; ++i) {
                 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
                 // Skip until the current IME/subtype is found.
-                if (imi.equals(item.mImi) && item.mSubtypeId == currentSubtypeId) {
+                if (imi.equals(item.mImi) && item.mSubtypeIndex == currentSubtypeIndex) {
                     return i;
                 }
             }
@@ -414,14 +420,14 @@
          */
         private int getUsageRank(@NonNull InputMethodInfo imi,
                 @Nullable InputMethodSubtype subtype) {
-            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
             final int numItems = mUsageHistoryOfSubtypeListItemIndex.length;
             for (int usageRank = 0; usageRank < numItems; usageRank++) {
                 final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
                 final ImeSubtypeListItem subtypeListItem =
                         mImeSubtypeList.get(subtypeListItemIndex);
                 if (subtypeListItem.mImi.equals(imi)
-                        && subtypeListItem.mSubtypeId == currentSubtypeId) {
+                        && subtypeListItem.mSubtypeIndex == currentSubtypeIndex) {
                     return usageRank;
                 }
             }
@@ -575,11 +581,11 @@
         @IntRange(from = -1)
         private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
                 boolean useRecency) {
-            final int subtypeIndex = calculateSubtypeId(imi, subtype);
+            final int subtypeIndex = calculateSubtypeIndex(imi, subtype);
             for (int i = 0; i < mItems.size(); i++) {
                 final int mappedIndex = useRecency ? mRecencyMap[i] : i;
                 final var item = mItems.get(mappedIndex);
-                if (item.mImi.equals(imi) && item.mSubtypeId == subtypeIndex) {
+                if (item.mImi.equals(imi) && item.mSubtypeIndex == subtypeIndex) {
                     return i;
                 }
             }
@@ -591,13 +597,13 @@
             pw.println(prefix + "Static order:");
             for (int i = 0; i < mItems.size(); ++i) {
                 final var item = mItems.get(i);
-                pw.println(prefix + "i=" + i + " item=" + item);
+                pw.println(prefix + "  i=" + i + " item=" + item);
             }
             pw.println(prefix + "Recency order:");
             for (int i = 0; i < mRecencyMap.length; ++i) {
                 final int index = mRecencyMap[i];
                 final var item = mItems.get(index);
-                pw.println(prefix + "i=" + i + " item=" + item);
+                pw.println(prefix + "  i=" + i + " item=" + item);
             }
         }
     }
@@ -800,7 +806,7 @@
                     pw.println(prefix + "mHardwareRotationList:");
                     mHardwareRotationList.dump(pw, prefix + "  ");
                 }
-                pw.println("User action since last switch: " + mUserActionSinceSwitch);
+                pw.println(prefix + "User action since last switch: " + mUserActionSinceSwitch);
             }
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 361cdbb..da35fe7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -59,7 +59,7 @@
  */
 final class InputMethodUtils {
     public static final boolean DEBUG = false;
-    static final int NOT_A_SUBTYPE_ID = -1;
+    static final int NOT_A_SUBTYPE_INDEX = -1;
     private static final String TAG = "InputMethodUtils";
 
     // The string for enabled input method is saved as follows:
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 1b4c0d6..f615b52 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,7 +51,6 @@
     static final String SUBTYPE_MODE_ANY = null;
     static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
 
-    static final int NOT_A_SUBTYPE_ID = -1;
     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
             "EnabledWhenDefaultIsNotAsciiCapable";
 
@@ -103,10 +105,19 @@
     }
 
     static boolean isValidSubtypeHashCode(InputMethodInfo imi, int subtypeHashCode) {
-        return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+        return getSubtypeIndexFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_INDEX;
     }
 
-    static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+    /**
+     * Returns the index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+     *
+     * @param imi             {@link InputMethodInfo} to be queried about
+     * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be queried about
+     *
+     * @return The index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+     *         {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if not found
+     */
+    static int getSubtypeIndexFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
         if (imi != null) {
             final int subtypeCount = imi.getSubtypeCount();
             for (int i = 0; i < subtypeCount; ++i) {
@@ -116,7 +127,7 @@
                 }
             }
         }
-        return NOT_A_SUBTYPE_ID;
+        return NOT_A_SUBTYPE_INDEX;
     }
 
     private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
@@ -242,7 +253,7 @@
      *                                    most applicable subtype, it will return the first subtype
      *                                    matched with mode
      *
-     * @return the most applicable subtypeId
+     * @return the most applicable {@link InputMethodSubtype}
      */
     static InputMethodSubtype findLastResortApplicableSubtype(
             List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
@@ -310,15 +321,15 @@
             @Nullable InputMethodSubtype currentSubtype) {
         final int userId = settings.getUserId();
         final int selectedSubtypeHashCode = SecureSettingsWrapper.getInt(
-                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID, userId);
-        if (selectedSubtypeHashCode != NOT_A_SUBTYPE_ID && currentSubtype != null
+                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, INVALID_SUBTYPE_HASHCODE, userId);
+        if (selectedSubtypeHashCode != INVALID_SUBTYPE_HASHCODE && currentSubtype != null
                 && isValidSubtypeHashCode(imi, currentSubtype.hashCode())) {
             return currentSubtype;
         }
 
-        final int subtypeId = settings.getSelectedInputMethodSubtypeId(imi.getId());
-        if (subtypeId != NOT_A_SUBTYPE_ID) {
-            return imi.getSubtypeAt(subtypeId);
+        final int subtypeIndex = settings.getSelectedInputMethodSubtypeIndex(imi.getId());
+        if (subtypeIndex != NOT_A_SUBTYPE_INDEX) {
+            return imi.getSubtypeAt(subtypeIndex);
         }
 
         // If there are no selected subtypes, the framework will try to find the most applicable
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index 28394c6a..96da17e 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -19,14 +19,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -149,11 +152,30 @@
     String mLastEnabledInputMethodsStr = "";
 
     /**
+     * A temporary solution to Bug 356879517, where we need to emulate the previous single-user mode
+     * behavior for KeyboardLayoutManager.
+     *
+     * <p>TODO(b/357663774): Remove this workaround</p>
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    Pair<InputMethodSubtypeHandle, InputMethodSubtype> mSubtypeForKeyboardLayoutMapping;
+
+    /**
      * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
      */
     @NonNull
     final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
 
+
+    /**
+     * {@code true} if the user storage is considered to be unlocked.
+     *
+     * @see com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)
+     */
+    @NonNull
+    final AtomicBoolean mIsUnlockingOrUnlocked = new AtomicBoolean(false);
+
     /**
      * Intended to be instantiated only from this file.
      */
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 53b6796..17f6561 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -622,16 +622,6 @@
 
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
-    @GuardedBy("mUidRulesFirstLock")
-    final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
 
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mUidRulesFirstLock")
@@ -4589,7 +4579,7 @@
     @VisibleForTesting
     @GuardedBy("mUidRulesFirstLock")
     void updateRestrictedModeAllowlistUL() {
-        mUidFirewallRestrictedModeRules.clear();
+        final SparseIntArray uidRules = new SparseIntArray();
         forEachUid("updateRestrictedModeAllowlist", uid -> {
             synchronized (mUidRulesFirstLock) {
                 final int effectiveBlockedReasons = updateBlockedReasonsForRestrictedModeUL(
@@ -4599,13 +4589,13 @@
                 // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
                 // non-default rules.
                 if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
-                    mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                    uidRules.append(uid, newFirewallRule);
                 }
             }
         });
         if (mRestrictedNetworkingMode) {
             // firewall rules only need to be set when this mode is being enabled.
-            setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, mUidFirewallRestrictedModeRules);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, uidRules);
         }
         enableFirewallChainUL(FIREWALL_CHAIN_RESTRICTED, mRestrictedNetworkingMode);
     }
@@ -4689,8 +4679,7 @@
     void updateRulesForPowerSaveUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL");
         try {
-            updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
-                    mUidFirewallPowerSaveRules);
+            updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4705,8 +4694,7 @@
     void updateRulesForDeviceIdleUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForDeviceIdleUL");
         try {
-            updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE,
-                    mUidFirewallDozableRules);
+            updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4720,13 +4708,11 @@
     // NOTE: since both fw_dozable and fw_powersave uses the same map
     // (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
     @GuardedBy("mUidRulesFirstLock")
-    private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain,
-            SparseIntArray rules) {
+    private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain) {
         if (enabled) {
             // Sync the allowlists before enabling the chain.  We don't care about the rules if
             // we are disabling the chain.
-            final SparseIntArray uidRules = rules;
-            uidRules.clear();
+            final SparseIntArray uidRules = new SparseIntArray();
             final List<UserInfo> users = mUserManager.getUsers();
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 UserInfo user = users.get(ui);
@@ -4755,9 +4741,7 @@
     private void updateRulesForBackgroundChainUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
         try {
-            final SparseIntArray uidRules = mUidFirewallBackgroundRules;
-            uidRules.clear();
-
+            final SparseIntArray uidRules = new SparseIntArray();
             final List<UserInfo> users = mUserManager.getUsers();
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 final UserInfo user = users.get(ui);
@@ -4794,17 +4778,17 @@
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL");
         try {
             if (mLowPowerStandbyActive) {
-                mUidFirewallLowPowerStandbyModeRules.clear();
+                final SparseIntArray uidRules = new SparseIntArray();
                 for (int i = mUidState.size() - 1; i >= 0; i--) {
                     final int uid = mUidState.keyAt(i);
                     final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
                     if (hasInternetPermissionUL(uid) && (effectiveBlockedReasons
                                     & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
-                        mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
+                        uidRules.put(uid, FIREWALL_RULE_ALLOW);
                     }
                 }
                 setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY,
-                        mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE);
+                        uidRules, CHAIN_TOGGLE_ENABLE);
             } else {
                 setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE);
             }
@@ -4822,10 +4806,8 @@
         final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
         if (mUidState.contains(uid)
                 && (effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
-            mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
             setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
         } else {
-            mUidFirewallLowPowerStandbyModeRules.delete(uid);
             setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
         }
     }
@@ -5313,16 +5295,11 @@
         mActivityManagerInternal.onUidBlockedReasonsChanged(uid, BLOCKED_REASON_NONE);
         mUidPolicy.delete(uid);
         mUidFirewallStandbyRules.delete(uid);
-        mUidFirewallDozableRules.delete(uid);
-        mUidFirewallPowerSaveRules.delete(uid);
-        mUidFirewallBackgroundRules.delete(uid);
         mBackgroundTransitioningUids.delete(uid);
         mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
         mAppIdleTempWhitelistAppIds.delete(uid);
-        mUidFirewallRestrictedModeRules.delete(uid);
-        mUidFirewallLowPowerStandbyModeRules.delete(uid);
         synchronized (mUidStateCallbackInfos) {
             mUidStateCallbackInfos.remove(uid);
         }
@@ -6269,18 +6246,8 @@
                     "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
         }
         try {
-            if (chain == FIREWALL_CHAIN_DOZABLE) {
-                mUidFirewallDozableRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_STANDBY) {
+            if (chain == FIREWALL_CHAIN_STANDBY) {
                 mUidFirewallStandbyRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
-                mUidFirewallPowerSaveRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_RESTRICTED) {
-                mUidFirewallRestrictedModeRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
-                mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
-            } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
-                mUidFirewallBackgroundRules.put(uid, rule);
             }
             // Note that we do not need keep a separate cache of uid rules for chains that we do
             // not call #setUidFirewallRulesUL for.
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index bd551fb..b4459cb 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -1194,9 +1194,9 @@
         }
 
         boolean shouldIgnoreNotification(final NotificationRecord record) {
-            // Ignore group summaries
-            return (record.getSbn().isGroup() && record.getSbn().getNotification()
-                    .isGroupSummary());
+            // Ignore auto-group summaries => don't count them as app-posted notifications
+            // for the cooldown budget
+            return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b164a52..8c280ed 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1676,7 +1676,11 @@
         if (config != null) {
             if (forRestore) {
                 config.user = userId;
-                if (!Flags.modesUi()) {
+                if (Flags.modesUi()) {
+                    if (config.manualRule != null) {
+                        config.manualRule.condition = null; // don't restore transient state
+                    }
+                } else {
                     config.manualRule = null;  // don't restore the manual rule
                 }
             }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b7dfd8d..55280b4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2585,12 +2585,31 @@
         boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
 
         if (libName != null && libVersion >= 0) {
+            final int beforeUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+            // If the lib already exists in the outPs#getUsesSdkLibraries, don't add it
+            // into the array and update its information below
             outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesSdkLibraries(), libName));
-            outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
-                    outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
-            outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBoolean(
-                    outPs.getUsesSdkLibrariesOptional(), optional));
+
+            // If the lib has already been added before, update the other information
+            final int afterUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+            if (beforeUsesSdkLibrariesLength == afterUsesSdkLibrariesLength) {
+                final int index = ArrayUtils.indexOf(outPs.getUsesSdkLibraries(), libName);
+                final long[] usesSdkLibrariesVersionsMajor =
+                        outPs.getUsesSdkLibrariesVersionsMajor();
+                usesSdkLibrariesVersionsMajor[index] = libVersion;
+                outPs.setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersionsMajor);
+
+                final boolean[] usesSdkLibrariesOptional = outPs.getUsesSdkLibrariesOptional();
+                usesSdkLibrariesOptional[index] = optional;
+                outPs.setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
+            } else {
+                outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
+                        outPs.getUsesSdkLibrariesVersionsMajor(), libVersion,
+                        /* allowDuplicates= */ true));
+                outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBooleanDuplicatesAllowed(
+                        outPs.getUsesSdkLibrariesOptional(), optional));
+            }
         }
 
         XmlUtils.skipCurrentTag(parser);
@@ -2602,10 +2621,24 @@
         long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
 
         if (libName != null && libVersion >= 0) {
+            final int beforeUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+            // If the lib already exists in the outPs#getUsesStaticLibraries, don't add it
+            // into the array and update its information below
             outPs.setUsesStaticLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesStaticLibraries(), libName));
-            outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
-                    outPs.getUsesStaticLibrariesVersions(), libVersion));
+
+            // If the lib has already been added before, update the version
+            final int afterUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+            if (beforeUsesStaticLibrariesLength == afterUsesStaticLibrariesLength) {
+                final int index = ArrayUtils.indexOf(outPs.getUsesStaticLibraries(), libName);
+                final long[] usesStaticLibrariesVersions = outPs.getUsesStaticLibrariesVersions();
+                usesStaticLibrariesVersions[index] = libVersion;
+                outPs.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions);
+            } else {
+                outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
+                        outPs.getUsesStaticLibrariesVersions(), libVersion,
+                        /* allowDuplicates= */ true));
+            }
         }
 
         XmlUtils.skipCurrentTag(parser);
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index cefecbc..5a45186 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -30,6 +30,7 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -39,7 +40,6 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -49,8 +49,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.util.XmlUtils;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -61,6 +61,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages quick launch shortcuts by:
@@ -123,6 +124,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final InputManagerInternal mInputManagerInternal;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
     private UserHandle mCurrentUser;
@@ -136,6 +138,7 @@
                     mRoleIntents.remove(roleName);
                 }, UserHandle.ALL);
         mCurrentUser = currentUser;
+        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         loadShortcuts();
     }
 
@@ -473,7 +476,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category + " role=" + role);
                 }
-                logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
+                notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -494,22 +497,19 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
-            logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(shortcutIntent));
+            notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
-    private void logKeyboardShortcut(KeyEvent event, KeyboardLogEvent logEvent) {
-        mHandler.post(() -> handleKeyboardLogging(event, logEvent));
-    }
-
-    private void handleKeyboardLogging(KeyEvent event, KeyboardLogEvent logEvent) {
-        final InputManager inputManager = mContext.getSystemService(InputManager.class);
-        final InputDevice inputDevice = inputManager != null
-                ? inputManager.getInputDevice(event.getDeviceId()) : null;
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
-                logEvent, event.getMetaState(), event.getKeyCode());
+    private void notifyKeyboardShortcutTriggered(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+            return;
+        }
+        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
     }
 
     /**
@@ -708,6 +708,97 @@
         return context.getString(resid);
     };
 
+
+    /**
+     * Find Keyboard shortcut event corresponding to intent filter category. Returns
+     * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found}
+     */
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getSystemShortcutFromIntent(Intent intent) {
+        Intent selectorIntent = intent.getSelector();
+        if (selectorIntent != null) {
+            Set<String> selectorCategories = selectorIntent.getCategories();
+            if (selectorCategories != null && !selectorCategories.isEmpty()) {
+                for (String intentCategory : selectorCategories) {
+                    int systemShortcut = getEventFromSelectorCategory(intentCategory);
+                    if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+                        continue;
+                    }
+                    return systemShortcut;
+                }
+            }
+        }
+
+        // The shortcut may be targeting a system role rather than using an intent selector,
+        // so check for that.
+        String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
+        if (!TextUtils.isEmpty(role)) {
+            return getLogEventFromRole(role);
+        }
+
+        Set<String> intentCategories = intent.getCategories();
+        if (intentCategories == null || intentCategories.isEmpty()
+                || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+        if (intent.getComponent() == null) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+
+        // TODO(b/280423320): Add new field package name associated in the
+        //  KeyboardShortcutEvent atom and log it accordingly.
+        return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    }
+
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getEventFromSelectorCategory(String category) {
+        switch (category) {
+            case Intent.CATEGORY_APP_BROWSER:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+            case Intent.CATEGORY_APP_EMAIL:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL;
+            case Intent.CATEGORY_APP_CONTACTS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS;
+            case Intent.CATEGORY_APP_CALENDAR:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR;
+            case Intent.CATEGORY_APP_CALCULATOR:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR;
+            case Intent.CATEGORY_APP_MUSIC:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC;
+            case Intent.CATEGORY_APP_MAPS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS;
+            case Intent.CATEGORY_APP_MESSAGING:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+            case Intent.CATEGORY_APP_GALLERY:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY;
+            case Intent.CATEGORY_APP_FILES:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES;
+            case Intent.CATEGORY_APP_WEATHER:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER;
+            case Intent.CATEGORY_APP_FITNESS:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS;
+            default:
+                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Find KeyboardLogEvent corresponding to the provide system role name.
+     * Returns {@code null} if no matching event found.
+     */
+    @KeyboardSystemShortcut.SystemShortcut
+    private static int getLogEventFromRole(String role) {
+        if (RoleManager.ROLE_BROWSER.equals(role)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+        } else if (RoleManager.ROLE_SMS.equals(role)) {
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+        } else {
+            Log.w(TAG, "Keyboard shortcut to launch "
+                    + role + " not supported for logging");
+            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+        }
+    }
+
     void dump(String prefix, PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw,  "  ", prefix);
         ipw.println("ModifierShortcutManager shortcuts:");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 21d6c64..720c1c2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -164,8 +165,6 @@
 import android.os.Trace;
 import android.os.UEventObserver;
 import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -229,8 +228,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
 import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -238,8 +235,6 @@
 import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
 import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.vibrator.HapticFeedbackVibrationProvider;
-import com.android.server.vibrator.VibratorFrameworkStatsLogger;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -462,7 +457,6 @@
     PackageManager mPackageManager;
     SideFpsEventHandler mSideFpsEventHandler;
     LockPatternUtils mLockPatternUtils;
-    private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
     private boolean mHasFeatureAuto;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
@@ -737,7 +731,6 @@
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
-    private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
     private static final int MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE = 27;
 
     private class PolicyHandler extends Handler {
@@ -825,9 +818,6 @@
                     handleSwitchKeyboardLayout(object.keyEvent, object.direction,
                             object.focusedToken);
                     break;
-                case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
-                    handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
-                    break;
                 case MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE:
                     final int keyCode = msg.arg1;
                     final long downTime = (Long) msg.obj;
@@ -1829,7 +1819,7 @@
     }
 
     private void handleShortPressOnHome(KeyEvent event) {
-        logKeyboardSystemsEvent(event, KeyboardLogEvent.HOME);
+        notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME);
 
         // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
         final HdmiControl hdmiControl = getHdmiControl();
@@ -2063,7 +2053,8 @@
             }
             switch (mDoubleTapOnHomeBehavior) {
                 case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
                     mHomeConsumed = true;
                     toggleRecentApps();
                     break;
@@ -2091,19 +2082,23 @@
                 case LONG_PRESS_HOME_ALL_APPS:
                     if (mHasFeatureLeanback) {
                         launchAllAppsAction();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                     }
                     break;
                 case LONG_PRESS_HOME_ASSIST:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                     launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
                     break;
                 case LONG_PRESS_HOME_NOTIFICATION_PANEL:
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     toggleNotificationPanel();
                     break;
                 default:
@@ -2388,8 +2383,6 @@
         mContext.registerReceiver(mMultiuserReceiver, filter);
 
         mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
-        mHapticFeedbackVibrationProvider =
-                new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator);
 
         mGlobalKeyManager = new GlobalKeyManager(mContext);
 
@@ -3292,39 +3285,29 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
-    /**
-     * Log the keyboard shortcuts without blocking the current thread.
-     *
-     * We won't log keyboard events when the input device is null
-     * or when it is virtual.
-     */
-    private void handleKeyboardSystemEvent(KeyboardLogEvent keyboardLogEvent, KeyEvent event) {
-        final InputDevice inputDevice = mInputManager.getInputDevice(event.getDeviceId());
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
-                keyboardLogEvent, event.getMetaState(), event.getKeyCode());
-        event.recycle();
-    }
-
-    private void logKeyboardSystemsEventOnActionUp(KeyEvent event,
-            KeyboardLogEvent keyboardSystemEvent) {
+    private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
         if (event.getAction() != KeyEvent.ACTION_UP) {
             return;
         }
-        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+        notifyKeyboardShortcutTriggered(event, systemShortcut);
     }
 
-    private void logKeyboardSystemsEventOnActionDown(KeyEvent event,
-            KeyboardLogEvent keyboardSystemEvent) {
+    private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return;
         }
-        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+        notifyKeyboardShortcutTriggered(event, systemShortcut);
     }
 
-    private void logKeyboardSystemsEvent(KeyEvent event, KeyboardLogEvent keyboardSystemEvent) {
-        KeyEvent eventToLog = KeyEvent.obtain(event);
-        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, keyboardSystemEvent.getIntValue(), 0,
-                eventToLog).sendToTarget();
+    private void notifyKeyboardShortcutTriggered(KeyEvent event,
+            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+            return;
+        }
+        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
     }
 
     @Override
@@ -3434,7 +3417,8 @@
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3443,7 +3427,8 @@
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
                     }
                 }
                 return true;
@@ -3452,7 +3437,8 @@
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
                             deviceId, event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                     return true;
                 }
                 break;
@@ -3465,14 +3451,16 @@
             case KeyEvent.KEYCODE_I:
                 if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
                 if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LOCK_SCREEN);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN);
                     return true;
                 }
                 break;
@@ -3480,10 +3468,12 @@
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_NOTES);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     }
                     return true;
                 }
@@ -3491,7 +3481,8 @@
             case KeyEvent.KEYCODE_S:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TAKE_SCREENSHOT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT);
                     return true;
                 }
                 break;
@@ -3504,14 +3495,16 @@
                         } catch (RemoteException e) {
                             Slog.d(TAG, "Error taking bugreport", e);
                         }
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TRIGGER_BUG_REPORT);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT);
                         return true;
                     }
                 }
                 // fall through
             case KeyEvent.KEYCODE_ESCAPE:
                 if (firstDown && event.isMetaPressed()) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                     injectBackGesture(event.getDownTime());
                     return true;
                 }
@@ -3520,7 +3513,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION);
                         return true;
                     }
                 }
@@ -3530,7 +3524,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE);
                         return true;
                     }
                 }
@@ -3540,12 +3535,15 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(true /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
                     } else {
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                         injectBackGesture(event.getDownTime());
                     }
                     return true;
@@ -3556,11 +3554,13 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 false /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
                         return true;
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(false /* leftOrTop */);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
                         return true;
                     }
                 }
@@ -3568,7 +3568,8 @@
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_SHORTCUT_HELPER);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER);
                     return true;
                 }
                 break;
@@ -3620,25 +3621,32 @@
                             | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
+
+                    int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
+                            ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN
+                            : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP;
+                    notifyKeyboardShortcutTriggered(event, systemShortcut);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
                 if (!down) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE);
                 }
                 return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -3665,7 +3673,8 @@
                 if (firstDown && !keyguardOn && isUserSetupComplete()) {
                     if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                         return true;
                     } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
@@ -3674,7 +3683,8 @@
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
                             return true;
                         }
                     }
@@ -3687,17 +3697,20 @@
                         Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                         msg.setAsynchronous(true);
                         msg.sendToTarget();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                     }
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -3705,7 +3718,8 @@
                     switch (mSearchKeyBehavior) {
                         case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH);
                             return true;
                         }
                         case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
@@ -3718,7 +3732,8 @@
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, focusedToken, direction);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3737,11 +3752,13 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
-                            logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+                            notifyKeyboardShortcutTriggered(event,
+                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3769,14 +3786,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                         return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_CAPS_LOCK:
                 if (!down) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
                 }
                 break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
@@ -3790,10 +3809,12 @@
                 if (firstDown) {
                     if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         toggleNotificationPanel();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
                         showSystemSettings();
-                        logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+                        notifyKeyboardShortcutTriggered(event,
+                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
                     }
                 }
                 return true;
@@ -4739,7 +4760,8 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
                 if (down) {
                     // There may have other embedded activities on the same Task. Try to move the
                     // focus before processing the back event.
@@ -4760,8 +4782,12 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                logKeyboardSystemsEventOnActionDown(event,
-                        KeyboardLogEvent.getVolumeEvent(keyCode));
+                int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN
+                        ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN
+                        : keyCode == KEYCODE_VOLUME_UP
+                                ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP
+                                : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE;
+                notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut);
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4862,7 +4888,8 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4872,7 +4899,8 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4895,14 +4923,16 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SYSTEM_NAVIGATION);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4918,7 +4948,8 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4929,7 +4960,8 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.WAKEUP);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4938,7 +4970,8 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SYSTEM_MUTE);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4953,7 +4986,8 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
-                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.MEDIA_KEY);
+                notifyKeyboardShortcutTriggeredOnActionUp(event,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -4998,7 +5032,8 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5009,7 +5044,8 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT);
+                    notifyKeyboardShortcutTriggered(event,
+                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5975,10 +6011,10 @@
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
         if (safeMode) {
-            performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+            performHapticFeedback(
                     HapticFeedbackConstants.SAFE_MODE_ENABLED,
-                    "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                    0 /* privFlags */);
+                    "Safe Mode Enabled" /* reason */,
+                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
         }
     }
 
@@ -6447,9 +6483,13 @@
                 Settings.Global.THEATER_MODE_ON, 0) == 1;
     }
 
-    private boolean performHapticFeedback(int effectId, String reason) {
-        return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
-            effectId, reason, 0 /* flags */, 0 /* privFlags */);
+    private void performHapticFeedback(int effectId, String reason) {
+        performHapticFeedback(effectId, reason, 0 /* flags */);
+    }
+
+    private void performHapticFeedback(
+            int effectId, String reason, @HapticFeedbackConstants.Flags int flags) {
+        mVibrator.performHapticFeedback(effectId, reason, flags, 0 /* privFlags */);
     }
 
     @Override
@@ -6457,25 +6497,6 @@
         return mGlobalKeyManager.shouldHandleGlobalKey(keyCode);
     }
 
-    @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
-            int flags, int privFlags) {
-        if (!mVibrator.hasVibrator()) {
-            return false;
-        }
-        VibrationEffect effect =
-                mHapticFeedbackVibrationProvider.getVibrationForHapticFeedback(effectId);
-        if (effect == null) {
-            return false;
-        }
-        VibrationAttributes attrs =
-                mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
-                        effectId, flags, privFlags);
-        VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
-        mVibrator.vibrate(uid, packageName, effect, reason, attrs);
-        return true;
-    }
-
 
     @Override
     public void keepScreenOnStartedLw() {
@@ -6651,7 +6672,6 @@
                 pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
         pw.print(prefix); pw.print("mKidsModeEnabled="); pw.println(mKidsModeEnabled);
 
-        mHapticFeedbackVibrationProvider.dump(prefix, pw);
         mGlobalKeyManager.dump(prefix, pw);
         mKeyCombinationManager.dump(prefix, pw);
         mSingleKeyGestureDetector.dump(prefix, pw);
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 1b394f6..67f5f27 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,7 +80,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.IDisplayFoldListener;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -1077,13 +1076,6 @@
     public void enableScreenAfterBoot();
 
     /**
-     * Call from application to perform haptic feedback on its window.
-     */
-    public boolean performHapticFeedback(int uid, String packageName, int effectId,
-            String reason, @HapticFeedbackConstants.Flags int flags,
-            @HapticFeedbackConstants.PrivateFlags int privFlags);
-
-    /**
      * Called when we have started keeping the screen on because a window
      * requesting this has become visible.
      */
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 503a726..65fc7b2 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.Flags;
@@ -28,6 +29,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.util.XmlUtils;
 import com.android.internal.vibrator.persistence.XmlParserException;
 import com.android.internal.vibrator.persistence.XmlReader;
 import com.android.internal.vibrator.persistence.XmlValidator;
@@ -39,6 +41,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.Reader;
 
 /**
  * Class that loads custom {@link VibrationEffect} to be performed for each
@@ -127,27 +130,19 @@
             Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
             return null;
         }
-        String customizationFile =
-                res.getString(
-                        com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
-        if (TextUtils.isEmpty(customizationFile)) {
-            Slog.d(TAG, "Customization file not configured.");
+
+        // Old loading path that reads customization from file at dir defined by config.
+        TypedXmlPullParser parser = readCustomizationFile(res);
+        if (parser == null) {
+            // When old loading path doesn't succeed, try loading customization from resources.
+            parser = readCustomizationResources(res);
+        }
+        if (parser == null) {
+            Slog.d(TAG, "No loadable haptic feedback customization.");
             return null;
         }
 
-        FileReader fileReader;
-        try {
-            fileReader = new FileReader(customizationFile);
-        } catch (FileNotFoundException e) {
-            Slog.d(TAG, "Specified customization file not found.");
-            return  null;
-        }
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-        parser.setInput(fileReader);
-
-        XmlReader.readDocumentStartTag(parser, TAG_CONSTANTS);
+        XmlUtils.beginDocument(parser, TAG_CONSTANTS);
         XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
         int rootDepth = parser.getDepth();
 
@@ -191,6 +186,46 @@
         return mapping;
     }
 
+    // TODO(b/356412421): deprecate old path related files.
+    private static TypedXmlPullParser readCustomizationFile(Resources res)
+            throws XmlPullParserException {
+        String customizationFile = res.getString(
+                com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+        if (TextUtils.isEmpty(customizationFile)) {
+            return null;
+        }
+
+        final Reader customizationReader;
+        try {
+            customizationReader = new FileReader(customizationFile);
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "Specified customization file not found.", e);
+            return null;
+        }
+
+        final TypedXmlPullParser parser;
+        parser = Xml.newFastPullParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(customizationReader);
+        Slog.d(TAG, "Successfully opened customization file.");
+        return parser;
+    }
+
+    private static TypedXmlPullParser readCustomizationResources(Resources res) {
+        if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
+            return null;
+        }
+        final XmlResourceParser resParser;
+        try {
+            resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "Haptic customization resource not found.", e);
+            return null;
+        }
+        Slog.d(TAG, "Successfully opened customization resource.");
+        return XmlUtils.makeTyped(resParser);
+    }
+
     /**
      * Represents an error while parsing a haptic feedback customization XML.
      */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7610d7d..76872cf 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -467,6 +467,14 @@
                 this, flags, privFlags);
     }
 
+    @Override // Binder call
+    public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+            int constant, int inputDeviceId, int inputSource, String reason, int flags,
+            int privFlags) {
+        performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant, inputDeviceId,
+                inputSource, reason, /* token= */ this, flags, privFlags);
+    }
+
     /**
      * An internal-only version of performHapticFeedback that allows the caller access to the
      * {@link HalVibration}.
@@ -501,6 +509,24 @@
     }
 
     /**
+     * An internal-only version of performHapticFeedback that allows the caller access to the
+     * {@link HalVibration}.
+     * The Vibration is only returned if it is ongoing after this method returns.
+     */
+    @VisibleForTesting
+    @Nullable
+    HalVibration performHapticFeedbackForInputDeviceInternal(
+            int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource,
+            String reason, IBinder token, int flags, int privFlags) {
+        // TODO(b/355543835): implement input device specific logic.
+        if (DEBUG) {
+            Slog.d(TAG, "performHapticFeedbackForInput: input device specific not implemented.");
+        }
+        return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+                this, flags, privFlags);
+    }
+
+    /**
      * An internal-only version of vibrate that allows the caller access to the
      * {@link HalVibration}.
      * The Vibration is only returned if it is ongoing after this method returns.
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 68f3738..19eba5f 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -330,6 +330,7 @@
         builder.setIsTranslucent(isTranslucent);
         builder.setWindowingMode(source.getWindowingMode());
         builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
+        builder.setUiMode(activity.getConfiguration().uiMode);
 
         final Configuration taskConfig = activity.getTask().getConfiguration();
         final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
@@ -448,7 +449,8 @@
                 mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
                 contentInsets, letterboxInsets, false /* isLowResolution */,
                 false /* isRealSnapshot */, source.getWindowingMode(),
-                attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */);
+                attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */,
+                topActivity.getConfiguration().uiMode /* uiMode */);
         return validateSnapshot(taskSnapshot);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7210098..a22db97 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5463,6 +5463,7 @@
             }
         }
 
+        mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible);
         onChildVisibilityRequested(visible);
 
         final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 509a060..8ef2693 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2846,6 +2846,11 @@
         } finally {
             SaferIntentUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
+                // Remove the empty task in case the activity was failed to be launched on the
+                // task that was restored from Recents.
+                if (!task.hasChild() && task.shouldRemoveSelfOnLastChildRemoval()) {
+                    task.removeIfPossible("start-from-recents");
+                }
                 mService.continueWindowLayout();
             }
         }
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
index b9bdc32..caff96b 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -35,7 +35,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
 
 /**
@@ -112,12 +111,10 @@
                 : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
     }
 
-    @VisibleForTesting
     boolean isHorizontalReachabilityEnabled() {
         return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
 
-    @VisibleForTesting
     boolean isVerticalReachabilityEnabled() {
         return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index 90bfddb..c3bf116 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -31,6 +31,8 @@
 import android.annotation.Nullable;
 import android.graphics.Rect;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.function.Supplier;
 
 /**
@@ -43,7 +45,8 @@
     @NonNull
     private final AppCompatConfiguration mAppCompatConfiguration;
     @Nullable
-    private Supplier<Rect> mLetterboxInnerBoundsSupplier;
+    @VisibleForTesting
+    Supplier<Rect> mLetterboxInnerBoundsSupplier;
 
     AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration) {
diff --git a/services/core/java/com/android/server/wm/AppSnapshotLoader.java b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
index ed65a2b..5b697e5 100644
--- a/services/core/java/com/android/server/wm/AppSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
@@ -203,7 +203,7 @@
                     new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
                             proto.letterboxInsetRight, proto.letterboxInsetBottom),
                     loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
-                    proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
+                    proto.appearance, proto.isTranslucent, false /* hasImeSurface */, proto.uiMode);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
             return null;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 48e1079..b4c7557 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -768,6 +768,48 @@
         }
     }
 
+    void onAppVisibilityChanged(@NonNull ActivityRecord ar, boolean visible) {
+        if (!mAnimationHandler.mComposed) {
+            return;
+        }
+
+        final boolean openingTransition = mAnimationHandler.mOpenAnimAdaptor
+                .mPreparedOpenTransition != null;
+        // Detect if another transition is collecting during predictive back animation.
+        if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
+                && ar.mTransitionController.isCollecting(ar)) {
+            final TransitionController controller = ar.mTransitionController;
+            boolean collectTask = false;
+            ActivityRecord changedActivity = null;
+            for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
+                final ActivityRecord next = mAnimationHandler.mOpenActivities[i];
+                if (next.mLaunchTaskBehind) {
+                    // collect previous activity, so shell side can handle the transition.
+                    controller.collect(next);
+                    collectTask = true;
+                    restoreLaunchBehind(next, true /* cancel */, false /* finishTransition */);
+                    changedActivity = next;
+                }
+            }
+            if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
+                    == AnimationHandler.TASK_SWITCH) {
+                final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
+                if (topTask != null) {
+                    WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
+                    while (parent != topTask && parent.isDescendantOf(topTask)) {
+                        controller.collect(parent);
+                        parent = parent.getParent();
+                    }
+                    controller.collect(topTask);
+                }
+            }
+            if (changedActivity != null) {
+                changedActivity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+                        true /* notifyClients */);
+            }
+        }
+    }
+
     // For shell transition
     /**
      * Check whether the transition targets was animated by back gesture animation.
@@ -784,7 +826,13 @@
             mAnimationHandler.markStartingSurfaceMatch(startTransaction);
             return;
         }
-        if (!isMonitoringFinishTransition() || targets.isEmpty()) {
+        if (targets.isEmpty()) {
+            return;
+        }
+        final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition();
+        if (migratePredictToTransition && !mAnimationHandler.mComposed) {
+            return;
+        } else if (!isMonitoringFinishTransition()) {
             return;
         }
         if (mAnimationHandler.hasTargetDetached()) {
@@ -808,20 +856,27 @@
                 mTmpCloseApps.add(wc);
             }
         }
-        final boolean matchAnimationTargets = isWaitBackTransition()
+        final boolean matchAnimationTargets;
+        if (migratePredictToTransition) {
+            matchAnimationTargets =
+                    mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        } else {
+            matchAnimationTargets = isWaitBackTransition()
                 && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
                 && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        }
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
                 mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
-        if (!matchAnimationTargets) {
+        // Don't cancel transition, let transition handler to handle it
+        if (!matchAnimationTargets && !migratePredictToTransition) {
             mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
         } else {
             if (mAnimationHandler.mPrepareCloseTransition != null) {
                 Slog.e(TAG, "Gesture animation is applied on another transition?");
             }
             mAnimationHandler.mPrepareCloseTransition = transition;
-            if (!Flags.migratePredictiveBackTransition()) {
+            if (!migratePredictToTransition) {
                 // Because the target will reparent to transition root, so it cannot be controlled
                 // by animation leash. Hide the close target when transition starts.
                 startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
@@ -839,7 +894,19 @@
     }
 
     boolean isMonitorTransitionTarget(WindowContainer wc) {
-        if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
+        if (Flags.migratePredictiveBackTransition()) {
+            if (!mAnimationHandler.mComposed) {
+                return false;
+            }
+            if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH
+                    && wc.asActivityRecord() != null
+                    || (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH
+                    && wc.asTask() != null)) {
+                return false;
+            }
+            return (mAnimationHandler.isTarget(wc, true /* open */)
+                    || mAnimationHandler.isTarget(wc, false /* open */));
+        } else if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
                 || (mAnimationHandler.mOpenAnimAdaptor != null
                 && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) {
             return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -1840,6 +1907,43 @@
         return openActivities;
     }
 
+    boolean restoreBackNavigation() {
+        if (!mAnimationHandler.mComposed) {
+            return false;
+        }
+        ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+        boolean changed = false;
+        if (penActivities != null) {
+            for (int i = penActivities.length - 1; i >= 0; --i) {
+                ActivityRecord resetActivity = penActivities[i];
+                if (resetActivity.mLaunchTaskBehind) {
+                    resetActivity.mTransitionController.collect(resetActivity);
+                    restoreLaunchBehind(resetActivity, true, false);
+                    changed = true;
+                }
+            }
+        }
+        return changed;
+    }
+
+    boolean restoreBackNavigationSetTransitionReady(Transition transition) {
+        if (!mAnimationHandler.mComposed) {
+            return false;
+        }
+        ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+        if (penActivities != null) {
+            for (int i = penActivities.length - 1; i >= 0; --i) {
+                ActivityRecord resetActivity = penActivities[i];
+                if (transition.isInTransition(resetActivity)) {
+                    resetActivity.mTransitionController.setReady(
+                            resetActivity.getDisplayContent(), true);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
         final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
         final ArrayList<ActivityRecord> affects = new ArrayList<>();
@@ -1919,9 +2023,12 @@
                 activity);
         if (cancel) {
             final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
-            if (migrateBackTransition && finishTransition) {
-                activity.commitVisibility(false /* visible */, false /* performLayout */,
-                        true /* fromTransition */);
+            // could be visible if transition is canceled due to top activity is finishing.
+            if (migrateBackTransition) {
+                if (finishTransition && !activity.shouldBeVisible()) {
+                    activity.commitVisibility(false /* visible */, false /* performLayout */,
+                            true /* fromTransition */);
+                }
             } else {
                 // Restore the launch-behind state
                 // TODO b/347168362 Change status directly during collecting for a transition.
@@ -2015,7 +2122,7 @@
         return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
     }
 
-    static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+    static boolean isSnapshotCompatible(@Nullable TaskSnapshot snapshot,
             @NonNull ActivityRecord[] visibleOpenActivities) {
         if (snapshot == null) {
             return false;
@@ -2026,6 +2133,12 @@
             if (!ar.isSnapshotOrientationCompatible(snapshot)) {
                 return false;
             }
+            final int appNightMode = ar.getConfiguration().uiMode
+                    & Configuration.UI_MODE_NIGHT_MASK;
+            final int snapshotNightMode = snapshot.getUiMode() & Configuration.UI_MODE_NIGHT_MASK;
+            if (appNightMode != snapshotNightMode) {
+                return false;
+            }
             oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
         }
         return oneComponentMatch;
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index f566df5..8f1828d 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -109,6 +109,13 @@
         if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
             return centerInScreen(idealSize, screenBounds);
         }
+        if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+                .hasFullscreenOverride()) {
+            // If the activity has a fullscreen override applied, it should be treated as
+            // resizeable and match the device orientation. Thus the ideal size can be
+            // applied.
+            return centerInScreen(idealSize, screenBounds);
+        }
         // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
         float appAspectRatio = calculateAspectRatio(task, activity);
         final float tdaWidth = stableBounds.width();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9c8c759..fcc6b11 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1804,9 +1804,7 @@
             return;
         }
         final int displayRotation = getRotation();
-        final int rotation = ar.isVisible()
-                ? ar.getWindowConfiguration().getDisplayRotation()
-                : mDisplayRotation.rotationForOrientation(orientation, displayRotation);
+        final int rotation = mDisplayRotation.rotationForOrientation(orientation, displayRotation);
         if (rotation == displayRotation) {
             return;
         }
@@ -6710,6 +6708,11 @@
         final boolean rotationChanged = super.setIgnoreOrientationRequest(ignoreOrientationRequest);
         mWmService.mDisplayWindowSettings.setIgnoreOrientationRequest(
                 this, mSetIgnoreOrientationRequest);
+        if (ignoreOrientationRequest && mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+            forAllActivities(r -> {
+                r.finishFixedRotationTransform();
+            });
+        }
         return rotationChanged;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8272e16..a5da5e7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1239,7 +1239,6 @@
      * @param lastRotation The most recently used rotation.
      * @return The surface rotation to use.
      */
-    @VisibleForTesting
     @Surface.Rotation
     int rotationForOrientation(@ScreenOrientation int orientation,
             @Surface.Rotation int lastRotation) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 32ec020..7c875c1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,22 +324,6 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
-    @Override
-    public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
-                    privFlags);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
-        performHapticFeedback(effectId, flags, privFlags);
-    }
-
     /* Drag/drop */
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 99e1e8b..0f9c001 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -19,8 +19,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -202,10 +204,12 @@
     }
 
     private static boolean isTransitionOpen(int type) {
-        return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
+        return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT
+                || type == TRANSIT_PREPARE_BACK_NAVIGATION;
     }
     private static boolean isTransitionClose(int type) {
-        return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
+        return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK
+                || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 16fcb09..1c8c245 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -313,6 +313,7 @@
             proto.appearance = mSnapshot.getAppearance();
             proto.isTranslucent = mSnapshot.isTranslucent();
             proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
+            proto.uiMode = mSnapshot.getUiMode();
             proto.id = mSnapshot.getId();
             final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
             final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index b7944d3..a83e8c7 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -116,7 +116,7 @@
 
         final Display display = DisplayManagerGlobal.getInstance()
                 .getRealDisplay(Display.DEFAULT_DISPLAY);
-        final DisplayCutout displayCutout = display.getCutout();
+        final DisplayCutout displayCutout = display != null ? display.getCutout() : null;
         if (displayCutout != null) {
             // Expand swipe start threshold such that we can catch touches that just start beyond
             // the notch area
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 2f46103..39b2635 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -92,6 +92,7 @@
         if (parent == null) {
             return;
         }
+        final boolean wasStarted = mTransparentPolicyState.isRunning();
         mTransparentPolicyState.reset();
         // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
         // opaque activity constraints because we're expecting the activity is already letterboxed.
@@ -102,6 +103,9 @@
         // We check if we need for some reason to skip the policy gievn the specific first
         // opaque activity
         if (shouldSkipTransparentPolicy(firstOpaqueActivity)) {
+            if (wasStarted) {
+                mActivityRecord.recomputeConfiguration();
+            }
             return;
         }
         mTransparentPolicyState.start(firstOpaqueActivity);
@@ -190,7 +194,6 @@
             // We skip letterboxing if the translucent activity doesn't have any
             // opaque activities beneath or the activity below is embedded which
             // never has letterbox.
-            mActivityRecord.recomputeConfiguration();
             return true;
         }
         if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
@@ -260,6 +263,10 @@
             mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
                     mActivityRecord, mFirstOpaqueActivity,
                     (opaqueConfig, transparentOverrideConfig) -> {
+                        if (!isPolicyEnabled()) {
+                            transparentOverrideConfig.unset();
+                            return transparentOverrideConfig;
+                        }
                         resetTranslucentOverrideConfig(transparentOverrideConfig);
                         final Rect parentBounds = parent.getWindowConfiguration().getBounds();
                         final Rect bounds = transparentOverrideConfig
@@ -313,7 +320,17 @@
         }
 
         private boolean isRunning() {
-            return mLetterboxConfigListener != null;
+            return mLetterboxConfigListener != null && isPolicyEnabled();
+        }
+
+        private boolean isPolicyEnabled() {
+            if (!mActivityRecord.mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+                return true;
+            }
+            // Do not enable the policy if the activity can affect display orientation.
+            final int orientation = mActivityRecord.getOverrideOrientation();
+            return orientation == SCREEN_ORIENTATION_UNSPECIFIED
+                    || !mActivityRecord.handlesOrientationChangeFromDescendant(orientation);
         }
 
         private void clearInheritedCompatDisplayInsets() {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0093e9d..58c48ad 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -59,6 +60,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
@@ -436,6 +438,11 @@
             // the same transition instead of relying on this possible racing condition.
             return;
         }
+        if (transition.mType == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
+                && mService.mBackNavigationController.restoreBackNavigationSetTransitionReady(
+                        transition)) {
+            return;
+        }
 
         transition.setAllReady();
     }
@@ -1386,6 +1393,12 @@
                 task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
                 break;
             }
+            case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: {
+                if (mService.mBackNavigationController.restoreBackNavigation()) {
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
         }
         return effects;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e646752..ec2fd3f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -55,6 +55,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -2989,6 +2990,25 @@
         return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
     }
 
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfig) {
+        super.resolveOverrideConfiguration(newParentConfig);
+        if (mActivityRecord != null) {
+            // Let the activity decide whether to apply the size override.
+            return;
+        }
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        resolvedConfig.seq = newParentConfig.seq;
+        applySizeOverrideIfNeeded(
+                getDisplayContent(),
+                mSession.mProcess.mInfo,
+                newParentConfig,
+                resolvedConfig,
+                (mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
+                false /* hasFixedRotationTransform */,
+                false /* hasCompatDisplayInsets */);
+    }
+
     /**
      * @return {@code true} if this window can receive touches based on among other things,
      * windowing state and recents animation state.
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 8ae4f9a..6afcae7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -45,7 +45,11 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.Display;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,6 +62,7 @@
 import com.android.internal.inputmethod.StartInputReason;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +75,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private DefaultImeVisibilityApplier mVisibilityApplier;
 
     @Before
@@ -112,6 +120,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_showIme() {
         final var statsToken = ImeTracker.Token.empty();
         synchronized (ImfLock.class) {
@@ -122,6 +131,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_hideIme() {
         final var statsToken = ImeTracker.Token.empty();
         synchronized (ImfLock.class) {
@@ -141,7 +151,12 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyHideSoftInput(true, true);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+        } else {
+            verifyHideSoftInput(true, true);
+        }
     }
 
     @Test
@@ -153,7 +168,12 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyHideSoftInput(true, true);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+        } else {
+            verifyHideSoftInput(true, true);
+        }
     }
 
     @Test
@@ -162,10 +182,16 @@
             mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
                     STATE_SHOW_IME_IMPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
         }
-        verifyShowSoftInput(true, true, 0 /* showFlags */);
+        if (Flags.refactorInsetsController()) {
+            verifySetImeVisibility(true /* setVisible */, true /* invoked */);
+            verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+        } else {
+            verifyShowSoftInput(true, true, 0 /* showFlags */);
+        }
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
         // Init a IME target client on the secondary display to show IME.
         mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
@@ -234,8 +260,10 @@
             verify(mVisibilityApplier).applyImeVisibility(
                     eq(mWindowToken), any(), eq(STATE_HIDE_IME),
                     eq(SoftInputShowHideReason.NOT_SET), eq(mUserId) /* userId */);
-            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
-                    eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+            if (!Flags.refactorInsetsController()) {
+                verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(eq(mWindowToken),
+                        eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+            }
         }
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index dc03732..aee7242 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -20,6 +20,7 @@
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_RECENT;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_STATIC;
 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.SwitchMode;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -61,7 +62,6 @@
     private static final boolean TEST_IS_VR_IME = false;
     private static final int TEST_IS_DEFAULT_RES_ID = 0;
     private static final String SYSTEM_LOCALE = "en_US";
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -103,7 +103,7 @@
                 TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
         if (subtypes == null) {
             items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
-                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
+                    NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
         } else {
             for (int i = 0; i < subtypes.size(); ++i) {
                 final String subtypeLocale = subtypeLocales.get(i);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index d7af443..c272430 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -924,6 +924,54 @@
     }
 
     @Test
+    public void testSameVersions_writeReadUsesStaticLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final String libTwo = "two";
+        final long versionOne = 311;
+        packageSetting.setUsesStaticLibraries(new String[] { libOne, libTwo });
+        packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionOne });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesStaticLibraries()[1], is(libTwo));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionOne));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[1], is(versionOne));
+    }
+
+    @Test
+    public void testSameLibNames_writeReadUsesStaticLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final long versionOne = 311;
+        final long versionTwo = 330;
+        packageSetting.setUsesStaticLibraries(new String[] { libOne, libOne});
+        packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionTwo });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getUsesStaticLibraries().length, is(1));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions().length, is(1));
+        assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionTwo));
+    }
+
+    @Test
     public void testWriteReadUsesSdkLibraries() {
         final Settings settingsUnderTest = makeSettings();
         final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -1008,6 +1056,65 @@
     }
 
     @Test
+    public void testSameVersions_writeReadUsesSdkLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final String libTwo = "two";
+        final long versionOne = 311;
+        final boolean optional = false;
+        packageSetting.setUsesSdkLibraries(new String[] { libOne, libTwo });
+        packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionOne });
+        packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optional, optional });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+        assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesSdkLibraries()[1], is(libTwo));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionOne));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[1], is(versionOne));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optional));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[1], is(optional));
+    }
+
+    @Test
+    public void testSameLibNames_writeReadUsesSdkLibraries() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String libOne = "one";
+        final long versionOne = 311;
+        final long versionTwo = 330;
+        final boolean optionalOne = false;
+        final boolean optionalTwo = true;
+        packageSetting.setUsesSdkLibraries(new String[] { libOne, libOne });
+        packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionTwo });
+        packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optionalOne, optionalTwo });
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+        assertThat(resultSetting.getUsesSdkLibraries().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional().length, is(1));
+        assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+        assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionTwo));
+        assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optionalTwo));
+    }
+
+    @Test
     public void testWriteReadPendingRestore() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index de70280..14ad15e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
 import static android.app.Notification.GROUP_ALERT_ALL;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
@@ -539,6 +541,36 @@
         return r;
     }
 
+    private NotificationRecord getAutogroupSummaryNotificationRecord(int id, String groupKey,
+            int groupAlertBehavior, UserHandle userHandle, String packageName) {
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setFlag(FLAG_GROUP_SUMMARY | FLAG_AUTOGROUP_SUMMARY, true);
+
+        int defaults = 0;
+        defaults |= Notification.DEFAULT_SOUND;
+        mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+                Notification.AUDIO_ATTRIBUTES_DEFAULT);
+
+        builder.setDefaults(defaults);
+        builder.setGroup(groupKey);
+        builder.setGroupAlertBehavior(groupAlertBehavior);
+        Notification n = builder.build();
+
+        Context context = spy(getContext());
+        PackageManager packageManager = spy(context.getPackageManager());
+        when(context.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(false);
+
+        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+                mUid, mPid, n, userHandle, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
+        mService.addNotification(r);
+        return r;
+    }
+
     //
     // Convenience functions for interacting with mocks
     //
@@ -2603,6 +2635,79 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_justSummaries() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+
+        // first update at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 0% volume
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_autogroupSummary() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // child should beep at 100% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // summary 0% volume (GROUP_ALERT_CHILDREN)
+        NotificationRecord summary = getAutogroupSummaryNotificationRecord(mId, "a",
+                GROUP_ALERT_CHILDREN, mUser, mPkg);
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(summary.isInterruptive());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 50% volume because autogroup summary was ignored
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index b955a79..60c4ac7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -31,6 +31,8 @@
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
 import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API;
+import static android.service.notification.ZenModeConfig.ZEN_TAG;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
@@ -283,8 +285,8 @@
         // the default value from the zen mode config.
         Policy policy = config.toNotificationPolicy(zenPolicy);
         assertEquals(Flags.modesUi()
-                ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
-                : config.isAllowPriorityChannels(),
+                        ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
+                        : config.isAllowPriorityChannels(),
                 policy.allowPriorityChannels());
     }
 
@@ -991,6 +993,58 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    public void testConfigXml_manualRule_upgradeWhenExisting() throws Exception {
+        // prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
+        // data on it because it's meant to indicate that the manual rule is on by merely existing.
+        ZenModeConfig config = new ZenModeConfig();
+        config.manualRule = new ZenModeConfig.ZenRule();
+        config.manualRule.enabled = true;
+        config.manualRule.pkg = "android";
+        config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        config.manualRule.conditionId = ZenModeConfig.toTimeCondition(mContext, 200, mUserId).id;
+        config.manualRule.enabler = "test";
+
+        // write out entire config xml
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig fromXml = readConfigXml(bais);
+
+        // The result should have a manual rule; it should have a non-null ZenPolicy and a condition
+        // whose state is true. The conditionId and enabler data should also be preserved.
+        assertThat(fromXml.manualRule).isNotNull();
+        assertThat(fromXml.manualRule.zenPolicy).isNotNull();
+        assertThat(fromXml.manualRule.condition).isNotNull();
+        assertThat(fromXml.manualRule.condition.state).isEqualTo(STATE_TRUE);
+        assertThat(fromXml.manualRule.conditionId).isEqualTo(config.manualRule.conditionId);
+        assertThat(fromXml.manualRule.enabler).isEqualTo("test");
+        assertThat(fromXml.isManualActive()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    public void testConfigXml_manualRule_doesNotTurnOnIfNotUpgrade() throws Exception {
+        // confirm that if the manual rule is already properly set up for modes_ui, it does not get
+        // turned on (set to condition with STATE_TRUE) when reading xml.
+
+        // getMutedAllConfig sets up the manual rule with a policy muting everything
+        ZenModeConfig config = getMutedAllConfig();
+        config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_FALSE, SOURCE_USER_ACTION);
+        assertThat(config.isManualActive()).isFalse();
+
+        // write out entire config xml
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig fromXml = readConfigXml(bais);
+
+        // The result should have a manual rule; it should not be changed from the previous rule.
+        assertThat(fromXml.manualRule).isEqualTo(config.manualRule);
+        assertThat(fromXml.isManualActive()).isFalse();
+    }
+
+    @Test
     public void testGetDescription_off() {
         ZenModeConfig config = new ZenModeConfig();
         if (!modesUi()) {
@@ -1238,4 +1292,25 @@
         parser.nextTag();
         return ZenModeConfig.readZenPolicyXml(parser);
     }
+
+    private void writeConfigXml(ZenModeConfig config, Integer version, boolean forBackup,
+            ByteArrayOutputStream os) throws IOException {
+        String tag = ZEN_TAG;
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(new BufferedOutputStream(os), "utf-8");
+        out.startDocument(null, true);
+        out.startTag(null, tag);
+        config.writeXml(out, version, forBackup);
+        out.endTag(null, tag);
+        out.endDocument();
+    }
+
+    private ZenModeConfig readConfigXml(ByteArrayInputStream is)
+            throws XmlPullParserException, IOException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(is), null);
+        parser.nextTag();
+        return ZenModeConfig.readXml(parser);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 63cf106..776a840 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1495,6 +1495,30 @@
     }
 
     @Test
+    public void testReadXmlRestore_doesNotEnableManualRule() throws Exception {
+        setupZenConfig();
+
+        // Turn on manual zen mode
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                ORIGIN_USER_IN_SYSTEMUI, "", "someCaller", SYSTEM_UID);
+        ZenModeConfig original = mZenModeHelper.mConfig.copy();
+        assertThat(original.isManualActive()).isTrue();
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(null);
+        TypedXmlPullParser parser = getParserForByteStream(baos);
+        mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL);
+
+        ZenModeConfig result = mZenModeHelper.getConfig();
+        assertThat(result.isManualActive()).isFalse();
+
+        // confirm that we do still keep policy information, modes_ui only; prior to modes_ui the
+        // entire rule is intentionally cleared
+        if (Flags.modesUi()) {
+            assertThat(result.manualRule.zenPolicy).isNotNull();
+        }
+    }
+
+    @Test
     public void testWriteXmlWithZenPolicy() throws Exception {
         final String ruleId = "customRule";
         setupZenConfig();
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 757bcd8..43ad44f 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,6 +32,7 @@
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
+        "junit-params",
         "mockito-target-inline-minus-junit4",
         "platform-test-annotations",
         "service-permission.stubs.system_server",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index 2b23b18..e0d05df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -16,16 +16,17 @@
 
 package com.android.server.vibrator;
 
-
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
 import static android.os.VibrationEffect.EFFECT_CLICK;
 
+import static com.android.internal.R.xml.haptic_feedback_customization;
 import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
@@ -39,10 +40,15 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -50,6 +56,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 
+@RunWith(JUnitParamsRunner.class)
 public class HapticFeedbackCustomizationTest {
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -78,21 +85,35 @@
     @Mock private Resources mResourcesMock;
     @Mock private VibratorInfo mVibratorInfoMock;
 
+    @Keep
+    private static Object[][] hapticFeedbackCustomizationTestArguments() {
+        // (boolean hasConfigFile, boolean hasRes).
+        return new Object[][] {{true, true}, {true, false}, {false, true}};
+    }
+
     @Before
     public void setUp() {
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
         mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+        mSetFlagsRule.disableFlags(
+                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
     }
 
     @Test
-    public void testParseCustomizations_noCustomization_success() throws Exception {
-        assertParseCustomizationsSucceeds(
-                /* xml= */ "<haptic-feedback-constants></haptic-feedback-constants>",
-                /* expectedCustomizations= */ new SparseArray<>());
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_noCustomization_success(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
+        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
+
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_featureFlagDisabled_returnsNull(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
         // Valid customization XML.
         String xml = "<haptic-feedback-constants>"
@@ -100,14 +121,16 @@
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        setupCustomizationFile(xml);
 
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
         assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
     }
 
     @Test
-    public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_oneVibrationCustomization_success(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
@@ -116,11 +139,13 @@
         SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
         expectedMapping.put(10, COMPOSITION_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_oneVibrationSelectCustomization_success() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_oneVibrationSelectCustomization_success(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-select>"
@@ -131,11 +156,13 @@
         SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
         expectedMapping.put(10, COMPOSITION_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_success() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_multipleCustomizations_success(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"1\">"
                 + COMPOSITION_VIBRATION_XML
@@ -162,11 +189,13 @@
         expectedMapping.put(150, PREDEFINED_VIBRATION);
         expectedMapping.put(10, WAVEFORM_VIBARTION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success()
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
+            boolean hasConfigFile, boolean hasRes)
                 throws Exception {
         makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
         String xml = "<haptic-feedback-constants>"
@@ -189,13 +218,16 @@
                 + "</vibration-select>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
 
-        assertParseCustomizationsSucceeds(xml, new SparseArray<>());
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success()
-                throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+            boolean hasConfigFile, boolean hasRes)
+            throws Exception {
         makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
         makeUnsupported(COMPOSITION_VIBRATION);
         String xml = "<haptic-feedback-constants>"
@@ -230,7 +262,7 @@
         expectedMapping.put(150, PREDEFINED_VIBRATION);
         expectedMapping.put(10, PREDEFINED_VIBRATION);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping);
+        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
     }
 
     @Test
@@ -252,12 +284,23 @@
     }
 
     @Test
-    public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException()
-            throws Exception {
+    public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
+        mSetFlagsRule.enableFlags(
+                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+        doThrow(new Resources.NotFoundException())
+                .when(mResourcesMock).getXml(haptic_feedback_customization);
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+                .isNull();
+    }
+
+    @Test
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         // The XML content is good, but the serialized vibration is not supported for haptic
         // feedback usage (i.e. repeating vibration).
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect>"
                 + "<waveform-effect>"
@@ -267,127 +310,139 @@
                 + "</waveform-effect>"
                 + "</vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_emptyXml_throwsException() throws Exception {
-        assertParseCustomizationsFails("");
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_emptyXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        assertParseCustomizationsFails("", hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_noVibrationXml_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_noVibrationXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"1\">"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badEffectId_throwsException() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badEffectId_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         // Negative id
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNegativeId = "<haptic-feedback-constants>"
                 + "<constant id=\"-10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // Non-numeral id
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNonNumericalId = "<haptic-feedback-constants>"
                 + "<constant id=\"xyz\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_malformedXml_throwsException() throws Exception {
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_malformedXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
         // No start "<constant>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoStartConstantTag = "<haptic-feedback-constants>"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No end "<constant>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoEndConstantTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No start "<haptic-feedback-constants>" tag
-        assertParseCustomizationsFails(
-                "<constant id=\"10\">"
+        String xmlNoStartCustomizationTag = "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
+                + "</haptic-feedback-constants>";
         // No end "<haptic-feedback-constants>" tag
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+        String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
-                + "</constant>");
+                + "</constant>";
+
+        assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badVibrationXml_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badVibrationXml_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlBad1 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<bad-vibration-effect></bad-vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad2 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad3 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-select>"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBad4 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</vibration-select>"
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_badConstantAttribute_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_badConstantAttribute_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
                 + "<constant iddddd=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
-
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+                + "</haptic-feedback-constants>";
+        String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
                 + "<constant id=\"10\" unwanted-attr=\"1\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
+        assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
     }
 
     @Test
-    public void testParseCustomizations_duplicateEffects_throwsException() throws Exception {
-        assertParseCustomizationsFails(
-                "<haptic-feedback-constants>"
+    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+    public void testParseCustomizations_duplicateEffects_throwsException(
+            boolean hasConfigFile, boolean hasRes) throws Exception {
+        String xmlDuplicateEffect = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
@@ -397,32 +452,46 @@
                 + "<constant id=\"11\">"
                 + PREDEFINED_VIBRATION_XML
                 + "</constant>"
-                + "</haptic-feedback-constants>");
+                + "</haptic-feedback-constants>";
+
+        assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
     }
 
-    private void assertParseCustomizationsSucceeds(
-            String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
-        setupCustomizationFile(xml);
+    private void assertParseCustomizationsSucceeds(String xml,
+            SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
+            boolean hasRes) throws Exception {
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
         assertThat(expectedCustomizations.contentEquals(
                 HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
-                        .isTrue();
+                .isTrue();
     }
 
-    private void assertParseCustomizationsFails(String xml) throws Exception {
-        setupCustomizationFile(xml);
-        assertThrows("Expected haptic feedback customization to fail for " + xml,
-                CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(
-                        mResourcesMock, mVibratorInfoMock));
-    }
-
-    private void assertParseCustomizationsFails() throws Exception {
+    private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+            throws Exception {
+        setupParseCustomizations(xml, hasConfigFile, hasRes);
         assertThrows("Expected haptic feedback customization to fail",
                 CustomizationParserException.class,
                 () ->  HapticFeedbackCustomization.loadVibrations(
                         mResourcesMock, mVibratorInfoMock));
     }
 
+    private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+            throws Exception {
+        clearFileAndResourceSetup();
+        if (hasConfigFile) {
+            setupCustomizationFile(xml);
+        }
+        if (hasRes) {
+            setupCustomizationResource(xml);
+        }
+    }
+
+    private void clearFileAndResourceSetup() {
+        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+                .thenReturn(null);
+        when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+    }
+
     private void setupCustomizationFile(String xml) throws Exception {
         File file = createFile(xml);
         setCustomizationFilePath(file.getAbsolutePath());
@@ -433,6 +502,13 @@
                 .thenReturn(path);
     }
 
+    private void setupCustomizationResource(String xml) throws Exception {
+        mSetFlagsRule.enableFlags(
+                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+        when(mResourcesMock.getXml(haptic_feedback_customization))
+                .thenReturn(FakeXmlResourceParser.fromXml(xml));
+    }
+
     private void makeSupported(VibrationEffect... effects) {
         for (VibrationEffect effect : effects) {
             when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(true);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e411a17..f009229 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2743,7 +2743,7 @@
     }
 
     private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
-                int constant, boolean always) throws InterruptedException {
+            int constant, boolean always) throws InterruptedException {
         HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
                 PACKAGE_NAME, constant, "some reason", service,
                 always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
new file mode 100644
index 0000000..ab7d43c
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.content.res.XmlResourceParser;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml(). This is borrowed
+ * from {@code ZenModeHelperTest}.
+ */
+public final class FakeXmlResourceParser implements XmlResourceParser {
+    private final TypedXmlPullParser mParser;
+
+    public FakeXmlResourceParser(TypedXmlPullParser parser) {
+        this.mParser = parser;
+    }
+
+    /** Create a {@link FakeXmlResourceParser} given a xml {@link String}. */
+    public static XmlResourceParser fromXml(String xml) throws XmlPullParserException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
+        return new FakeXmlResourceParser(parser);
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException {
+        return mParser.getEventType();
+    }
+
+    @Override
+    public void setFeature(String name, boolean state) throws XmlPullParserException {
+        mParser.setFeature(name, state);
+    }
+
+    @Override
+    public boolean getFeature(String name) {
+        return false;
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws XmlPullParserException {
+        mParser.setProperty(name, value);
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return mParser.getProperty(name);
+    }
+
+    @Override
+    public void setInput(Reader in) throws XmlPullParserException {
+        mParser.setInput(in);
+    }
+
+    @Override
+    public void setInput(InputStream inputStream, String inputEncoding)
+            throws XmlPullParserException {
+        mParser.setInput(inputStream, inputEncoding);
+    }
+
+    @Override
+    public String getInputEncoding() {
+        return mParser.getInputEncoding();
+    }
+
+    @Override
+    public void defineEntityReplacementText(String entityName, String replacementText)
+            throws XmlPullParserException {
+        mParser.defineEntityReplacementText(entityName, replacementText);
+    }
+
+    @Override
+    public int getNamespaceCount(int depth) throws XmlPullParserException {
+        return mParser.getNamespaceCount(depth);
+    }
+
+    @Override
+    public String getNamespacePrefix(int pos) throws XmlPullParserException {
+        return mParser.getNamespacePrefix(pos);
+    }
+
+    @Override
+    public String getNamespaceUri(int pos) throws XmlPullParserException {
+        return mParser.getNamespaceUri(pos);
+    }
+
+    @Override
+    public String getNamespace(String prefix) {
+        return mParser.getNamespace(prefix);
+    }
+
+    @Override
+    public int getDepth() {
+        return mParser.getDepth();
+    }
+
+    @Override
+    public String getPositionDescription() {
+        return mParser.getPositionDescription();
+    }
+
+    @Override
+    public int getLineNumber() {
+        return mParser.getLineNumber();
+    }
+
+    @Override
+    public int getColumnNumber() {
+        return mParser.getColumnNumber();
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException {
+        return mParser.isWhitespace();
+    }
+
+    @Override
+    public String getText() {
+        return mParser.getText();
+    }
+
+    @Override
+    public char[] getTextCharacters(int[] holderForStartAndLength) {
+        return mParser.getTextCharacters(holderForStartAndLength);
+    }
+
+    @Override
+    public String getNamespace() {
+        return mParser.getNamespace();
+    }
+
+    @Override
+    public String getName() {
+        return mParser.getName();
+    }
+
+    @Override
+    public String getPrefix() {
+        return mParser.getPrefix();
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException {
+        return false;
+    }
+
+    @Override
+    public int getAttributeCount() {
+        return mParser.getAttributeCount();
+    }
+
+    @Override
+    public int next() throws IOException, XmlPullParserException {
+        return mParser.next();
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException {
+        return mParser.next();
+    }
+
+    @Override
+    public void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException {
+        mParser.require(type, namespace, name);
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException {
+        return mParser.nextText();
+    }
+
+    @Override
+    public String getAttributeNamespace(int index) {
+        return "";
+    }
+
+    @Override
+    public String getAttributeName(int index) {
+        return mParser.getAttributeName(index);
+    }
+
+    @Override
+    public String getAttributePrefix(int index) {
+        return mParser.getAttributePrefix(index);
+    }
+
+    @Override
+    public String getAttributeType(int index) {
+        return mParser.getAttributeType(index);
+    }
+
+    @Override
+    public boolean isAttributeDefault(int index) {
+        return mParser.isAttributeDefault(index);
+    }
+
+    @Override
+    public String getAttributeValue(int index) {
+        return mParser.getAttributeValue(index);
+    }
+
+    @Override
+    public String getAttributeValue(String namespace, String name) {
+        return mParser.getAttributeValue(namespace, name);
+    }
+
+    @Override
+    public int getAttributeNameResource(int index) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeListValue(String namespace, String attribute, String[] options,
+            int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+            boolean defaultValue) {
+        return false;
+    }
+
+    @Override
+    public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public float getAttributeFloatValue(String namespace, String attribute,
+            float defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeListValue(int index, String[] options, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+        return false;
+    }
+
+    @Override
+    public int getAttributeResourceValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeIntValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public float getAttributeFloatValue(int index, float defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public String getIdAttribute() {
+        return null;
+    }
+
+    @Override
+    public String getClassAttribute() {
+        return null;
+    }
+
+    @Override
+    public int getIdAttributeResourceValue(int defaultValue) {
+        return 0;
+    }
+
+    @Override
+    public int getStyleAttribute() {
+        return 0;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public int nextTag() throws IOException, XmlPullParserException {
+        return mParser.nextTag();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
new file mode 100644
index 0000000..e26f3e0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
+
+import android.hardware.input.KeyboardSystemShortcut;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.KeyEvent;
+
+import androidx.test.filters.MediumTest;
+
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@MediumTest
+@RunWith(JUnitParamsRunner.class)
+public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
+    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
+    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
+    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
+    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
+    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
+    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
+    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
+
+    @Keep
+    private static Object[][] shortcutTestArguments() {
+        // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState
+        return new Object[][]{
+                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON},
+                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER,
+                        META_ON},
+                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS,
+                        KeyEvent.KEYCODE_RECENT_APPS, 0},
+                {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        META_ON},
+                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        ALT_ON},
+                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK,
+                        KeyEvent.KEYCODE_BACK, 0},
+                {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE,
+                        META_ON},
+                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON},
+                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyEvent.KEYCODE_APP_SWITCH, 0},
+                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_ASSIST, 0},
+                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+                        META_ON},
+                {"VOICE_ASSIST key -> Launch Voice Assistant",
+                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+                        KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+                        KeyEvent.KEYCODE_I, META_ON},
+                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_N, META_ON},
+                {"NOTIFICATION key -> Toggle Notification Panel",
+                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_NOTIFICATION,
+                        0},
+                {"Meta + Ctrl + S -> Take Screenshot",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+                        META_ON | CTRL_ON},
+                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+                        KeyEvent.KEYCODE_SLASH, META_ON},
+                {"BRIGHTNESS_UP key -> Increase Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+                        KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP,
+                        KeyEvent.KEYCODE_VOLUME_UP, 0},
+                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN,
+                        KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE,
+                        KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+                {"ALL_APPS key -> Open App Drawer in Accessibility mode",
+                        new int[]{KeyEvent.KEYCODE_ALL_APPS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_ALL_APPS, 0},
+                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+                        KeyEvent.KEYCODE_SEARCH, 0},
+                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+                        KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY,
+                        META_ON},
+                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY,
+                        META_ON | ALT_ON},
+                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY,
+                        META_ON | ALT_ON},
+                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+                        KeyEvent.KEYCODE_CAPS_LOCK, 0},
+                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+                        0},
+                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_UP,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyEvent.KEYCODE_DPAD_RIGHT,
+                        META_ON | CTRL_ON},
+                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+                        META_ON},
+                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N,
+                        META_ON | CTRL_ON},
+                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+                        0},
+                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER,
+                        KeyEvent.KEYCODE_TV_POWER, 0},
+                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+                        0},
+                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+                        0},
+                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+                        0},
+                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+                        0},
+                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+                {"MEDIA_PLAY_PAUSE key -> Media Control",
+                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_B, META_ON},
+                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_EXPLORER, 0},
+                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_C, META_ON},
+                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_CONTACTS, 0},
+                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_E, META_ON},
+                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_ENVELOPE, 0},
+                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_K, META_ON},
+                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_CALENDAR, 0},
+                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_P, META_ON},
+                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_MUSIC, 0},
+                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_U, META_ON},
+                {"CALCULATOR key -> Launch Default Calculator",
+                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_CALCULATOR, 0},
+                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+                        KeyEvent.KEYCODE_M, META_ON},
+                {"Meta + S -> Launch Default Messaging App",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+                        KeyEvent.KEYCODE_S, META_ON},
+                {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE,
+                        KeyEvent.KEYCODE_DPAD_DOWN,
+                        META_ON | CTRL_ON}};
+    }
+
+    @Keep
+    private static Object[][] longPressOnHomeTestArguments() {
+        // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"Long press HOME key -> Toggle Notification panel",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Toggle Notification panel",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_ENTER,
+                        META_ON},
+                {"Long press META + H -> Toggle Notification panel",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_H, META_ON},
+                {"Long press HOME key -> Launch assistant",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Launch assistant",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Long press META + H -> Launch assistant",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
+                        META_ON},
+                {"Long press HOME key -> Open App Drawer in Accessibility mode",
+                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Long press META + H -> Open App Drawer in Accessibility mode",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        LONG_PRESS_HOME_ALL_APPS,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyEvent.KEYCODE_H, META_ON}};
+    }
+
+    @Keep
+    private static Object[][] doubleTapOnHomeTestArguments() {
+        // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"Double tap HOME -> Open App switcher",
+                        new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME,
+                        0},
+                {"Double tap META + ENTER -> Open App switcher",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyEvent.KEYCODE_ENTER, META_ON},
+                {"Double tap META + H -> Open App switcher",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H,
+                        META_ON}};
+    }
+
+    @Keep
+    private static Object[][] settingsKeyTestArguments() {
+        // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
+                        SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
+                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyEvent.KEYCODE_SETTINGS, 0}};
+    }
+
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
+        mPhoneWindowManager.overrideLaunchHome();
+        mPhoneWindowManager.overrideSearchKeyBehavior(
+                PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
+        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.overrideSendBroadcast();
+        mPhoneWindowManager.overrideUserSetupComplete();
+        mPhoneWindowManager.setupAssistForLaunch();
+        mPhoneWindowManager.overrideTogglePanel();
+        mPhoneWindowManager.overrideInjectKeyEvent();
+    }
+
+    @Test
+    @Parameters(method = "shortcutTestArguments")
+    public void testShortcut(String testName, int[] testKeys,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+                expectedModifierState);
+    }
+
+    @Test
+    @Parameters(method = "longPressOnHomeTestArguments")
+    public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
+        sendLongPressKeyCombination(testKeys);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+
+    @Test
+    @Parameters(method = "doubleTapOnHomeTestArguments")
+    public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
+            int doubleTapOnHomeBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
+        sendKeyCombination(testKeys, 0 /* duration */);
+        sendKeyCombination(testKeys, 0 /* duration */);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+
+    @Test
+    @Parameters(method = "settingsKeyTestArguments")
+    public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
+        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+                expectedModifierState);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+    public void testBugreportShortcutPress() {
+        testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
+                new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
+                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
+                META_ON | CTRL_ON);
+    }
+
+    private void testShortcutInternal(String testName, int[] testKeys,
+            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            int expectedModifierState) {
+        sendKeyCombination(testKeys, 0 /* duration */);
+        mPhoneWindowManager.assertKeyboardShortcutTriggered(
+                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+                "Failed while executing " + testName);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
deleted file mode 100644
index aa28147..0000000
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.policy;
-
-import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
-import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
-
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.KeyEvent;
-
-import androidx.test.filters.MediumTest;
-
-import com.android.internal.annotations.Keep;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@MediumTest
-@RunWith(JUnitParamsRunner.class)
-public class ShortcutLoggingTests extends ShortcutKeyTestBase {
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    private static final int VENDOR_ID = 0x123;
-    private static final int PRODUCT_ID = 0x456;
-    private static final int DEVICE_BUS = 0x789;
-    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
-    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
-    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
-    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
-    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
-    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
-    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
-    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
-
-    @Keep
-    private static Object[][] shortcutTestArguments() {
-        // testName, testKeys, expectedLogEvent, expectedKey, expectedModifierState
-        return new Object[][]{
-                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_H, META_ON},
-                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, KeyboardLogEvent.HOME,
-                        KeyEvent.KEYCODE_HOME, 0},
-                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0},
-                {"Meta + Tab -> Open OVerview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON},
-                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
-                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
-                        KeyEvent.KEYCODE_BACK, 0},
-                {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_ESCAPE, META_ON},
-                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
-                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
-                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
-                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
-                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0},
-                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON},
-                {"VOICE_ASSIST key -> Launch Voice Assistant",
-                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
-                        KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0},
-                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
-                        KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON},
-                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON},
-                {"NOTIFICATION key -> Toggle Notification Panel",
-                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
-                        0},
-                {"Meta + Ctrl + S -> Take Screenshot",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
-                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
-                        KeyboardLogEvent.OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON},
-                {"BRIGHTNESS_UP key -> Increase Brightness",
-                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, KeyboardLogEvent.BRIGHTNESS_UP,
-                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
-                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
-                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
-                        KeyboardLogEvent.BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
-                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
-                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
-                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
-                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
-                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE,
-                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
-                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
-                        KeyboardLogEvent.VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0},
-                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
-                        KeyboardLogEvent.VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0},
-                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
-                        KeyboardLogEvent.VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0},
-                {"ALL_APPS key -> Open App Drawer in Accessibility mode",
-                        new int[]{KeyEvent.KEYCODE_ALL_APPS},
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0},
-                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
-                        KeyboardLogEvent.LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0},
-                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
-                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
-                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
-                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
-                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON},
-                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON},
-                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
-                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0},
-                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
-                        KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
-                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
-                        KeyboardLogEvent.MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
-                        META_ON | CTRL_ON},
-                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT,
-                        META_ON | CTRL_ON},
-                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
-                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT,
-                        META_ON | CTRL_ON},
-                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
-                        KeyboardLogEvent.LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON},
-                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardLogEvent.OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON},
-                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
-                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0},
-                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
-                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0},
-                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
-                        0},
-                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
-                        0},
-                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
-                        0},
-                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
-                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
-                        KeyboardLogEvent.SYSTEM_NAVIGATION,
-                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
-                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
-                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
-                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
-                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0},
-                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
-                        KeyboardLogEvent.WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
-                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
-                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0},
-                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
-                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
-                {"MEDIA_PLAY_PAUSE key -> Media Control",
-                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyboardLogEvent.MEDIA_KEY,
-                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
-                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON},
-                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0},
-                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON},
-                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0},
-                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON},
-                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0},
-                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON},
-                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0},
-                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON},
-                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0},
-                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON},
-                {"CALCULATOR key -> Launch Default Calculator",
-                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0},
-                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
-                {"Meta + S -> Launch Default Messaging App",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON},
-                {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
-                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
-                        KeyboardLogEvent.DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN,
-                        META_ON | CTRL_ON}};
-    }
-
-    @Keep
-    private static Object[][] longPressOnHomeTestArguments() {
-        // testName, testKeys, longPressOnHomeBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"Long press HOME key -> Toggle Notification panel",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Toggle Notification panel",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_ENTER,
-                        META_ON},
-                {"Long press META + H -> Toggle Notification panel",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_H, META_ON},
-                {"Long press HOME key -> Launch assistant",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Launch assistant",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Long press META + H -> Launch assistant",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, META_ON},
-                {"Long press HOME key -> Open App Drawer in Accessibility mode",
-                        new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Long press META + H -> Open App Drawer in Accessibility mode",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        LONG_PRESS_HOME_ALL_APPS, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS,
-                        KeyEvent.KEYCODE_H, META_ON}};
-    }
-
-    @Keep
-    private static Object[][] doubleTapOnHomeTestArguments() {
-        // testName, testKeys, doubleTapOnHomeBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"Double tap HOME -> Open App switcher",
-                        new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_HOME, 0},
-                {"Double tap META + ENTER -> Open App switcher",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, KeyboardLogEvent.APP_SWITCH,
-                        KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Double tap META + H -> Open App switcher",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}};
-    }
-
-    @Keep
-    private static Object[][] settingsKeyTestArguments() {
-        // testName, testKeys, settingsKeyBehavior, expectedLogEvent, expectedKey,
-        // expectedModifierState
-        return new Object[][]{
-                {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
-                        SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
-                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
-    }
-
-    @Before
-    public void setUp() {
-        setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
-        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
-        mPhoneWindowManager.overrideLaunchHome();
-        mPhoneWindowManager.overrideSearchKeyBehavior(
-                PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
-        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
-        mPhoneWindowManager.overrideStatusBarManagerInternal();
-        mPhoneWindowManager.overrideStartActivity();
-        mPhoneWindowManager.overrideSendBroadcast();
-        mPhoneWindowManager.overrideUserSetupComplete();
-        mPhoneWindowManager.setupAssistForLaunch();
-        mPhoneWindowManager.overrideTogglePanel();
-        mPhoneWindowManager.overrideInjectKeyEvent();
-    }
-
-    @Test
-    @Parameters(method = "shortcutTestArguments")
-    public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
-            int expectedKey, int expectedModifierState) {
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "longPressOnHomeTestArguments")
-    public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
-            KeyboardLogEvent expectedLogEvent, int expectedKey, int expectedModifierState) {
-        mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
-        sendLongPressKeyCombination(testKeys);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "doubleTapOnHomeTestArguments")
-    public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
-            int doubleTapOnHomeBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
-            int expectedModifierState) {
-        mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @Parameters(method = "settingsKeyTestArguments")
-    public void testSettingsKey(String testName, int[] testKeys,
-            int settingsKeyBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
-            int expectedModifierState) {
-        mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
-        sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
-                expectedKey, expectedModifierState, DEVICE_BUS,
-                "Failed while executing " + testName);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
-    public void testBugreportShortcutPress() {
-        sendKeyCombination(new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, 0);
-        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID,
-                KeyboardLogEvent.TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL, META_ON | CTRL_ON,
-                DEVICE_BUS, "Failed to log bugreport shortcut.");
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6f8c91c..f9b5c2a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -26,7 +26,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -50,6 +49,7 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.withSettings;
@@ -70,6 +70,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -85,7 +86,6 @@
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.view.Display;
-import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.autofill.AutofillManagerInternal;
@@ -93,11 +93,9 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.policy.KeyInterceptionInfo;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -269,7 +267,6 @@
         // Return mocked services: LocalServices.getService
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
-                .mockStatic(FrameworkStatsLog.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
@@ -583,19 +580,6 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
     }
 
-    void overrideKeyEventSource(int vendorId, int productId, int deviceBus) {
-        InputDevice device = new InputDevice.Builder()
-                .setId(1)
-                .setVendorId(vendorId)
-                .setProductId(productId)
-                .setDeviceBus(deviceBus)
-                .setSources(InputDevice.SOURCE_KEYBOARD)
-                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
-                .build();
-        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
-        doReturn(device).when(mInputManager).getInputDevice(anyInt());
-    }
-
     void overrideInjectKeyEvent() {
         doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
     }
@@ -820,12 +804,11 @@
         Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent());
     }
 
-    void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
-            int expectedKey, int expectedModifierState, int deviceBus, String errorMsg) {
+    void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut,
+            String errorMsg) {
         mTestLooper.dispatchAll();
-        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
-                        vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
-                        expectedModifierState, deviceBus), description(errorMsg));
+        verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered(
+                anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut));
     }
 
     void assertSwitchToTask(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 03d3029..2a53df9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -258,6 +258,6 @@
                 Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */,
                 new Rect() /* letterboxInsets*/, false /* isLowResolution */,
                 true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
-                false /* isTranslucent */, false /* hasImeSurface */);
+                false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index f8cf97e..a745724 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -36,9 +36,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.view.Surface;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.utils.TestComponentStack;
 
@@ -74,19 +77,36 @@
     private final int mDisplayHeight;
     private DisplayContent mDisplayContent;
 
+    @Nullable
+    private Consumer<ActivityRecord> mOnPostActivityCreation;
+
+    @Nullable
+    private Consumer<DisplayContent> mOnPostDisplayContentCreation;
+
     AppCompatActivityRobot(@NonNull WindowManagerService wm,
             @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
-            int displayWidth, int displayHeight) {
+            int displayWidth, int displayHeight,
+            @Nullable Consumer<ActivityRecord> onPostActivityCreation,
+            @Nullable Consumer<DisplayContent> onPostDisplayContentCreation) {
         mAtm = atm;
         mSupervisor = supervisor;
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
         mActivityStack = new TestComponentStack<>();
         mTaskStack = new TestComponentStack<>();
+        mOnPostActivityCreation = onPostActivityCreation;
+        mOnPostDisplayContentCreation = onPostDisplayContentCreation;
         createNewDisplay();
     }
 
     AppCompatActivityRobot(@NonNull WindowManagerService wm,
+            @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
+            int displayWidth, int displayHeight) {
+        this(wm, atm, supervisor, displayWidth, displayHeight, /* onPostActivityCreation */ null,
+                /* onPostDisplayContentCreation */ null);
+    }
+
+    AppCompatActivityRobot(@NonNull WindowManagerService wm,
             @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor) {
         this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     }
@@ -96,6 +116,10 @@
                 /* inNewDisplay */ false);
     }
 
+    void createActivityWithComponentWithoutTask() {
+        createActivityWithComponentInNewTask(/* inNewTask */ false, /* inNewDisplay */ false);
+    }
+
     void createActivityWithComponentInNewTask() {
         createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ false);
     }
@@ -104,7 +128,6 @@
         createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ true);
     }
 
-
     void configureTopActivity(float minAspect, float maxAspect, int screenOrientation,
             boolean isUnresizable) {
         prepareLimitedBounds(mActivityStack.top(), minAspect, maxAspect, screenOrientation,
@@ -130,6 +153,14 @@
         doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
     }
 
+    void configureTaskBounds(@NonNull Rect taskBounds) {
+        doReturn(taskBounds).when(mTaskStack.top()).getBounds();
+    }
+
+    void configureTopActivityBounds(@NonNull Rect activityBounds) {
+        doReturn(activityBounds).when(mActivityStack.top()).getBounds();
+    }
+
     @NonNull
     ActivityRecord top() {
         return mActivityStack.top();
@@ -169,6 +200,10 @@
                 .isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
     }
 
+    void setTopActivityInTransition(boolean inTransition) {
+        doReturn(inTransition).when(mActivityStack.top()).isInTransition();
+    }
+
     void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
                 .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
@@ -238,21 +273,20 @@
     void createNewDisplay() {
         mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
                 .build();
-        spyOn(mDisplayContent);
-        spyOnAppCompatCameraPolicy();
+        onPostDisplayContentCreation(mDisplayContent);
     }
 
     void createNewTask() {
         final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
                 .setDisplay(mDisplayContent).build();
-        pushTask(newTask);
+        mTaskStack.push(newTask);
     }
 
     void createNewTaskWithBaseActivity() {
         final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
                 .setCreateActivity(true)
                 .setDisplay(mDisplayContent).build();
-        pushTask(newTask);
+        mTaskStack.push(newTask);
         pushActivity(newTask.getTopNonFinishingActivity());
     }
 
@@ -378,6 +412,34 @@
         pushActivity(newActivity);
     }
 
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link ActivityRecord}. Common case is to invoke spyOn().
+     *
+     * @param activity The newly created {@link ActivityRecord}.
+     */
+    @CallSuper
+    void onPostActivityCreation(@NonNull ActivityRecord activity) {
+        spyOn(activity.mLetterboxUiController);
+        if (mOnPostActivityCreation != null) {
+            mOnPostActivityCreation.accept(activity);
+        }
+    }
+
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link DisplayContent}. Common case is to invoke spyOn().
+     *
+     * @param displayContent The newly created {@link DisplayContent}.
+     */
+    @CallSuper
+    void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+        spyOn(mDisplayContent);
+        if (mOnPostDisplayContentCreation != null) {
+            mOnPostDisplayContentCreation.accept(mDisplayContent);
+        }
+    }
+
     private void createActivityWithComponentInNewTask(boolean inNewTask, boolean inNewDisplay) {
         if (inNewDisplay) {
             createNewDisplay();
@@ -385,14 +447,16 @@
         if (inNewTask) {
             createNewTask();
         }
-        final ActivityRecord activity = new WindowTestsBase.ActivityBuilder(mAtm)
-                .setOnTop(true)
-                .setTask(mTaskStack.top())
+        final WindowTestsBase.ActivityBuilder activityBuilder =
+                new WindowTestsBase.ActivityBuilder(mAtm).setOnTop(true)
                 // Set the component to be that of the test class in order
                 // to enable compat changes
-                .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME))
-                .build();
-        pushActivity(activity);
+                .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME));
+        if (!mTaskStack.isEmpty()) {
+            // We put the Activity in the current task if any.
+            activityBuilder.setTask(mTaskStack.top());
+        }
+        pushActivity(activityBuilder.build());
     }
 
     /**
@@ -438,28 +502,6 @@
     // We add the activity to the stack and spyOn() on its properties.
     private void pushActivity(@NonNull ActivityRecord activity) {
         mActivityStack.push(activity);
-        spyOn(activity);
-        // TODO (b/351763164): Use these spyOn calls only when necessary.
-        spyOn(activity.mAppCompatController.getTransparentPolicy());
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
-        spyOn(activity.mAppCompatController.getAppCompatFocusOverrides());
-        spyOn(activity.mAppCompatController.getAppCompatResizeOverrides());
-        spyOn(activity.mLetterboxUiController);
-    }
-
-    private void pushTask(@NonNull Task task) {
-        spyOn(task);
-        mTaskStack.push(task);
-    }
-
-    private void spyOnAppCompatCameraPolicy() {
-        spyOn(mDisplayContent.mAppCompatCameraPolicy);
-        if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
-            spyOn(mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
-        }
-        if (mDisplayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
-            spyOn(mDisplayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
-        }
+        onPostActivityCreation(activity);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index a6fd112..1e40aa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -291,7 +291,6 @@
      * Runs a test scenario providing a Robot.
      */
     void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) {
-        spyOn(mWm.mAppCompatConfiguration);
         final AspectRatioOverridesRobotTest robot =
                 new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor);
         consumer.accept(robot);
@@ -305,6 +304,18 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        }
+
         void checkShouldApplyUserFullscreenOverride(boolean expected) {
             assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
                     .shouldApplyUserFullscreenOverride());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index de99f54..84ffcb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -387,6 +387,12 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
         void checkShouldRefreshActivityForCameraCompat(boolean expected) {
             Assert.assertEquals(getAppCompatCameraOverrides()
                     .shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 0b1bb0f..c42228d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -150,6 +150,12 @@
             super(wm, atm, supervisor);
         }
 
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+        }
+
         void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) {
             Assert.assertEquals(exists, activity().top().mDisplayContent
                     .mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 6592f26..40a5347 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -19,6 +19,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import androidx.annotation.NonNull;
@@ -80,4 +83,34 @@
         doReturn(aspectRatio).when(mAppCompatConfiguration)
                 .getFixedOrientationLetterboxAspectRatio();
     }
+
+    void setThinLetterboxWidthPx(int thinWidthPx) {
+        doReturn(thinWidthPx).when(mAppCompatConfiguration)
+                .getThinLetterboxWidthPx();
+    }
+
+    void setThinLetterboxHeightPx(int thinHeightPx) {
+        doReturn(thinHeightPx).when(mAppCompatConfiguration)
+                .getThinLetterboxHeightPx();
+    }
+
+    void checkToNextLeftStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForHorizontalReachabilityToNextLeftStop(anyBoolean());
+    }
+
+    void checkToNextRightStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForHorizontalReachabilityToNextRightStop(anyBoolean());
+    }
+
+    void checkToNextBottomStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForVerticalReachabilityToNextBottomStop(anyBoolean());
+    }
+
+    void checkToNextTopStop(boolean invoked) {
+        verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+                .movePositionForVerticalReachabilityToNextTopStop(anyBoolean());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 6c0d8c4..d9b5f37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -250,6 +250,12 @@
             mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierFake();
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
         // Useful to reduce timeout during tests
         void prepareMockedTime() {
             getTopOrientationOverrides().mOrientationOverridesState.mCurrentTimeMillisSupplier =
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index ad34a6b..f6d0744 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -536,6 +536,25 @@
             }
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+            if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+            }
+            if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+            }
+        }
+
         void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) {
             getTopOrientationOverrides().setRelaunchingAfterRequestedOrientationChanged(enabled);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
new file mode 100644
index 0000000..5ff8f02
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+import junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Test
+    public void testIsThinLetterboxed_NegativePx_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentWithoutTask();
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ -1);
+            robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ -1);
+            robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testIsThinLetterboxed_noTask_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentWithoutTask();
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+            robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+            robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+        });
+    }
+
+    @Test
+    public void testIsVerticalThinLetterboxed() {
+        runTestScenario((robot) -> {
+            robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+                // (task.width() - act.width()) / 2  = 5 < 10
+                a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+                // (task.width() - act.width()) / 2  = 10 = 10
+                a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+                // (task.width() - act.width()) / 2  = 11 > 10
+                a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+                robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+            });
+        });
+    }
+
+    @Test
+    public void testIsHorizontalThinLetterboxed() {
+        runTestScenario((robot) -> {
+            robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+                // (task.height() - act.height()) / 2  = 5 < 10
+                a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+                // (task.height() - act.height()) / 2  = 10 = 10
+                a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+                // (task.height() - act.height()) / 2  = 11 > 10
+                a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+                robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+            });
+        });
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ false);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ false);
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+        });
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+
+            robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+            robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+        });
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<ReachabilityOverridesRobotTest> consumer) {
+        spyOn(mWm.mAppCompatConfiguration);
+        final ReachabilityOverridesRobotTest robot =
+                new ReachabilityOverridesRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class ReachabilityOverridesRobotTest extends AppCompatRobotBase {
+
+        private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+        ReachabilityOverridesRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                    .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+        }
+
+        void configureIsVerticalThinLetterboxed(boolean isThin) {
+            doReturn(isThin).when(getAppCompatReachabilityOverrides())
+                    .isVerticalThinLetterboxed();
+        }
+
+        void configureIsHorizontalThinLetterboxed(boolean isThin) {
+            doReturn(isThin).when(getAppCompatReachabilityOverrides())
+                    .isHorizontalThinLetterboxed();
+        }
+
+        void checkIsVerticalThinLetterboxed(boolean expected) {
+            Assert.assertEquals(expected,
+                    getAppCompatReachabilityOverrides().isVerticalThinLetterboxed());
+        }
+
+        void checkIsHorizontalThinLetterboxed(boolean expected) {
+            Assert.assertEquals(expected,
+                    getAppCompatReachabilityOverrides().isHorizontalThinLetterboxed());
+        }
+
+        void checkAllowVerticalReachabilityForThinLetterbox(boolean expected) {
+            Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+                    .allowVerticalReachabilityForThinLetterbox());
+        }
+
+        void checkAllowHorizontalReachabilityForThinLetterbox(boolean expected) {
+            Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+                    .allowHorizontalReachabilityForThinLetterbox());
+        }
+
+        @NonNull
+        private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+        }
+
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
new file mode 100644
index 0000000..96734b3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityPolicy}.
+ * <p/>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityPolicyTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityPolicyTest extends WindowTestsBase {
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityDisabled_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_leftInnerFrame_moveToLeft() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(99, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ true);
+                c.checkToNextRightStop(/* invoked */ false);
+            });
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_rightInnerFrame_moveToRight() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(201, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ false);
+                c.checkToNextRightStop(/* invoked */ true);
+            });
+        });
+    }
+
+    @Test
+    public void handleHorizontalDoubleTap_intoInnerFrame_noMove() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableHorizontalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+            robot.doubleTapAt(150, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextLeftStop(/* invoked */ false);
+                c.checkToNextRightStop(/* invoked */ false);
+            });
+        });
+    }
+
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityDisabled_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+            robot.doubleTapAt(100, 100);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ true);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ false);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_topInnerFrame_moveToTop() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 99);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ true);
+                c.checkToNextBottomStop(/* invoked */ false);
+            });
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_bottomInnerFrame_moveToBottom() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 201);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ false);
+                c.checkToNextBottomStop(/* invoked */ true);
+            });
+        });
+    }
+
+    @Test
+    public void handleVerticalDoubleTap_intoInnerFrame_noMove() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponent();
+            robot.enableVerticalReachability(/* enabled */ true);
+            robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+            robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+            robot.doubleTapAt(100, 150);
+
+            robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+            robot.applyOnConf((c) -> {
+                c.checkToNextTopStop(/* invoked */ false);
+                c.checkToNextBottomStop(/* invoked */ false);
+            });
+        });
+    }
+
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<ReachabilityPolicyRobotTest> consumer) {
+        spyOn(mWm.mAppCompatConfiguration);
+        final ReachabilityPolicyRobotTest robot =
+                new ReachabilityPolicyRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class ReachabilityPolicyRobotTest extends AppCompatRobotBase {
+
+        private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+        ReachabilityPolicyRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                    .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+        }
+
+        void configureLetterboxInnerFrameWidth(int left, int right) {
+            doReturn(new Rect(left, /* top */ 0, right, /* bottom */ 100))
+                    .when(mLetterboxInnerBoundsSupplier).get();
+        }
+
+        void configureLetterboxInnerFrameHeight(int top, int bottom) {
+            doReturn(new Rect(/* left */ 0, top, /* right */ 100, bottom))
+                    .when(mLetterboxInnerBoundsSupplier).get();
+        }
+
+        void enableHorizontalReachability(boolean enabled) {
+            doReturn(enabled).when(getAppCompatReachabilityOverrides())
+                    .isHorizontalReachabilityEnabled();
+        }
+
+        void enableVerticalReachability(boolean enabled) {
+            doReturn(enabled).when(getAppCompatReachabilityOverrides())
+                    .isVerticalReachabilityEnabled();
+        }
+
+        void doubleTapAt(int x, int y) {
+            getAppCompatReachabilityPolicy().handleDoubleTap(x, y);
+        }
+
+        void checkLetterboxInnerFrameProvidedInvoked(boolean invoked) {
+            verify(mLetterboxInnerBoundsSupplier, times(invoked ? 1 : 0)).get();
+        }
+
+        @NonNull
+        private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+        }
+
+        @NonNull
+        private AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
+            return activity().top().mAppCompatController.getAppCompatReachabilityPolicy();
+        }
+
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index 8fc1a77..cade213 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -39,7 +39,7 @@
 
 /**
  * Test class for {@link AppCompatResizeOverrides}.
- * <p>
+ * <p/>
  * Build/Install/Run:
  * atest WmTests:AppCompatResizeOverridesTest
  */
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 6939f97..4e58e1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 
 import java.util.function.Consumer;
@@ -42,7 +43,8 @@
             @NonNull ActivityTaskSupervisor supervisor,
             int displayWidth, int displayHeight) {
         mActivityRobot = new AppCompatActivityRobot(wm, atm, supervisor,
-                displayWidth, displayHeight);
+                displayWidth, displayHeight, this::onPostActivityCreation,
+                this::onPostDisplayContentCreation);
         mConfigurationRobot =
                 new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
         mOptPropRobot = new AppCompatComponentPropRobot(wm);
@@ -54,6 +56,26 @@
         this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     }
 
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link ActivityRecord}. Common case is to invoke spyOn().
+     *
+     * @param activity THe newly created {@link ActivityRecord}.
+     */
+    @CallSuper
+    void onPostActivityCreation(@NonNull ActivityRecord activity) {
+    }
+
+    /**
+     * Specific Robots can override this method to add operation to run on a newly created
+     * {@link DisplayContent}. Common case is to invoke spyOn().
+     *
+     * @param displayContent THe newly created {@link DisplayContent}.
+     */
+    @CallSuper
+    void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+    }
+
     @NonNull
     AppCompatConfigurationRobot conf() {
         return mConfigurationRobot;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
index 3cfbb9e..5af7093 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
@@ -62,6 +62,10 @@
         consumer.accept(mActivityRobot);
     }
 
+    void setDisplayContentBounds(int left, int top, int right, int bottom) {
+        mActivityRobot.displayContent().setBounds(left, top, right, bottom);
+    }
+
     void launchTransparentActivity() {
         mActivityRobot.launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1,
                 SCREEN_ORIENTATION_PORTRAIT, /* transparent */ true,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 9e242ee..21fac9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.Presubmit;
@@ -42,7 +44,10 @@
     @Test
     public void getLetterboxReasonString_inSizeCompatMode() {
         runTestScenario((robot) -> {
-            robot.activity().setTopActivityInSizeCompatMode(/* inScm */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setTopActivityInSizeCompatMode(/* inScm */ true);
+            });
 
             robot.checkTopActivityLetterboxReason(/* expected */ "SIZE_COMPAT_MODE");
         });
@@ -51,7 +56,10 @@
     @Test
     public void getLetterboxReasonString_fixedOrientation() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ true);
 
@@ -62,7 +70,10 @@
     @Test
     public void getLetterboxReasonString_isLetterboxedForDisplayCutout() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ true);
@@ -74,7 +85,10 @@
     @Test
     public void getLetterboxReasonString_aspectRatio() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -87,7 +101,10 @@
     @Test
     public void getLetterboxReasonString_unknownReason() {
         runTestScenario((robot) -> {
-            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+            });
             robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
                     /* forFixedOrientationAndAspectRatio */ false);
             robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -97,7 +114,6 @@
         });
     }
 
-
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -114,10 +130,15 @@
                 @NonNull ActivityTaskManagerService atm,
                 @NonNull ActivityTaskSupervisor supervisor) {
             super(wm, atm, supervisor);
-            activity().createActivityWithComponent();
             mWindowState = Mockito.mock(WindowState.class);
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+        }
+
         void setIsLetterboxedForFixedOrientationAndAspectRatio(
                 boolean forFixedOrientationAndAspectRatio) {
             when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index b687042..07e95d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -31,6 +31,7 @@
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
 import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
@@ -231,6 +232,56 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_userFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isUserFullscreenOverrideEnabled();
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultLandscapeBounds_landscapeDevice_systemFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isSystemOverrideToFullscreenEnabled();
+
+        final int desiredWidth =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
@@ -332,6 +383,56 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_userFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isUserFullscreenOverrideEnabled();
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+    public void testDefaultPortraitBounds_portraitDevice_systemFullscreenOverride() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        doReturn(true).when(
+                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .isSystemOverrideToFullscreenEnabled();
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index e77c14a..eacb8e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -82,12 +82,15 @@
     public void setUp() {
         DisplayInfo di = new DisplayInfo(mDisplayInfo);
         Mode defaultMode = di.getDefaultMode();
-        di.supportedModes = new Mode[] {
-                new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
-                new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
-                new Mode(LOW_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
-        };
+        Mode hiMode = new Mode(1,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90);
+        Mode midMode = new Mode(2,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70);
+        Mode lowMode = new Mode(LOW_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60);
+
+        di.supportedModes = new Mode[] { hiMode, midMode };
+        di.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
         di.defaultModeId = 1;
         mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
         when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 33df5d8..695068a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -23,11 +23,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -36,8 +34,6 @@
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -49,7 +45,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
-import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -296,106 +291,6 @@
     }
 
     @Test
-    public void testIsVerticalThinLetterboxed() {
-        // Vertical thin letterbox disabled
-        doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxHeightPx();
-        final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-        // Define a Task 100x100
-        final Task task = mock(Task.class);
-        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
-        doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxHeightPx();
-
-        // Vertical thin letterbox disabled without Task
-        doReturn(null).when(mActivity).getTask();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-        // Assign a Task for the Activity
-        doReturn(task).when(mActivity).getTask();
-
-        // (task.width() - act.width()) / 2  = 5 < 10
-        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
-        // (task.width() - act.width()) / 2  = 10 = 10
-        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
-        // (task.width() - act.width()) / 2  = 11 > 10
-        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
-        assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
-    }
-
-    @Test
-    public void testIsHorizontalThinLetterboxed() {
-        // Horizontal thin letterbox disabled
-        doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxWidthPx();
-        final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-        // Define a Task 100x100
-        final Task task = mock(Task.class);
-        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
-        doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getThinLetterboxWidthPx();
-
-        // Vertical thin letterbox disabled without Task
-        doReturn(null).when(mActivity).getTask();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-        // Assign a Task for the Activity
-        doReturn(task).when(mActivity).getTask();
-
-        // (task.height() - act.height()) / 2  = 5 < 10
-        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
-        // (task.height() - act.height()) / 2  = 10 = 10
-        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
-        assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
-        // (task.height() - act.height()) / 2  = 11 > 10
-        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
-        assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
-        final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
-        spyOn(reachabilityOverrides);
-        doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertFalse(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertFalse(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
-        doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
-        final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
-        spyOn(reachabilityOverrides);
-        doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
-        doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
-        doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
-        assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-    }
-
-    @Test
     public void testIsLetterboxEducationEnabled() {
         mController.isLetterboxEducationEnabled();
         verify(mAppCompatConfiguration).getIsEducationEnabled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 33f7035..b95f621 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1413,7 +1413,7 @@
                 Surface.ROTATION_0, taskSize, new Rect() /* contentInsets */,
                 new Rect() /* letterboxInsets*/, false /* isLowResolution */,
                 true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
-                false /* isTranslucent */, false /* hasImeSurface */);
+                false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 7ebf9ac..3fa38bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -66,7 +66,6 @@
 
     private RefreshRatePolicy mPolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
-    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
@@ -98,18 +97,14 @@
     @Before
     public void setUp() {
         Mode defaultMode = mDisplayInfo.getDefaultMode();
-        mDisplayInfo.supportedModes = new Mode[] {
-                new Mode(HI_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        HI_REFRESH_RATE),
-                new Mode(MID_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        MID_REFRESH_RATE),
-                new Mode(LOW_MODE_ID,
-                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
-                        LOW_REFRESH_RATE),
-        };
-        mDisplayInfo.appsSupportedModes = mDisplayInfo.supportedModes;
+        Mode hiMode = new Mode(HI_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), HI_REFRESH_RATE);
+        Mode midMode = new Mode(MID_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), MID_REFRESH_RATE);
+        Mode lowMode = new Mode(LOW_MODE_ID,
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
+        mDisplayInfo.supportedModes = new Mode[] { hiMode, midMode };
+        mDisplayInfo.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
         mDisplayInfo.defaultModeId = HI_MODE_ID;
         mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 84c0696..1e0cef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -227,7 +227,7 @@
                     // disk.
                     false /* isLowResolution */,
                     mIsRealSnapshot, mWindowingMode, mSystemUiVisibility, mIsTranslucent,
-                    false /* hasImeSurface */);
+                    false /* hasImeSurface */, 0 /* uiMode */);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 4b0668f..d62c626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,12 +270,6 @@
     }
 
     @Override
-    public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
-            int flags, int privFlags) {
-        return false;
-    }
-
-    @Override
     public void keepScreenOnStartedLw() {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index cbf17c4..a0641cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -22,8 +22,11 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.mockito.Mockito.clearInvocations;
 
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -207,6 +210,25 @@
         });
     }
 
+    @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
+    @Test
+    public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
+        runTestScenario(robot -> robot.transparentActivity(ta -> ta.applyOnActivity((a) -> {
+            a.configureTopActivityIgnoreOrientationRequest(false);
+            // The translucent activity is SCREEN_ORIENTATION_PORTRAIT.
+            ta.launchTransparentActivityInTask();
+            // Though TransparentPolicyState will be started, it won't be considered as running.
+            ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+
+            // If the display changes to ignore orientation request, e.g. unfold, the policy should
+            // take effect.
+            a.configureTopActivityIgnoreOrientationRequest(true);
+            ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ true);
+            ta.setDisplayContentBounds(0, 0, 900, 1800);
+            ta.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1);
+        })), /* displayWidth */ 500,  /* displayHeight */ 1000);
+    }
+
     @Test
     public void testTranslucentActivitiesDontGoInSizeCompatMode() {
         runTestScenario((robot) -> {
@@ -343,6 +365,12 @@
             activity().createNewTaskWithBaseActivity();
         }
 
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getTransparentPolicy());
+        }
+
         void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) {
             consumer.accept(mTransparentActivityRobot);
         }
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 5524541..a01a720 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -267,6 +267,17 @@
             "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
 
     /**
+     * Bundle key for the {@code resolvedBundle} passed to {@link #onDownloadSubscription(
+     * int, int, DownloadableSubscription, boolean, boolean, Bundle)}. The value is a
+     * {@link String} for the package name of the app calling the
+     * {@link EuiccManager#downloadSubscription(int, DownloadableSubscription, PendingIntent)} API.
+     * This is to be used by LPA to determine the app that is requesting the download.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.service.euicc.extra.PACKAGE_NAME";
+
+    /**
      * Intent extra set for resolution requests containing an int indicating the current card Id.
      */
     public static final String EXTRA_RESOLUTION_CARD_ID =
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bd9270..ad6db2d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -412,6 +412,14 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26;
 
+    /**
+     * Emergency call is in progress.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -440,7 +448,8 @@
             SATELLITE_RESULT_ILLEGAL_STATE,
             SATELLITE_RESULT_MODEM_TIMEOUT,
             SATELLITE_RESULT_LOCATION_DISABLED,
-            SATELLITE_RESULT_LOCATION_NOT_AVAILABLE
+            SATELLITE_RESULT_LOCATION_NOT_AVAILABLE,
+            SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 5976657..71f3033 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -47,6 +47,10 @@
     compile_dex: true,
     default_to_stubs: true,
     dist_group: "android",
+
+    // This module cannot generate stubs from the api signature files as stubs depends on the
+    // private APIs, which are not visible in the api signature files.
+    build_from_text_stub: false,
 }
 
 java_library {
diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
new file mode 100644
index 0000000..24d7291
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyboardSystemShortcutListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardSystemShortcutListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardSystemShortcutListenerTest {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val HOME_SHORTCUT = KeyboardSystemShortcut(
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = SetFlagsRule()
+
+    private val testLooper = TestLooper()
+    private val executor = HandlerExecutor(Handler(testLooper.looper))
+    private var registeredListener: IKeyboardSystemShortcutListener? = null
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle keyboard system shortcut listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered keyboard system shortcut listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any())
+
+        // Handle keyboard system shortcut listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) {
+        registeredListener!!.onKeyboardSystemShortcutTriggered(
+            id,
+            shortcut.keycodes,
+            shortcut.modifierState,
+            shortcut.systemShortcut
+        )
+    }
+
+    @Test
+    fun testListenerHasCorrectSystemShortcutNotified() {
+        var callbackCount = 0
+
+        // Add a keyboard system shortcut listener
+        inputManager.registerKeyboardSystemShortcutListener(executor) {
+            deviceId: Int, systemShortcut: KeyboardSystemShortcut ->
+            assertEquals(DEVICE_ID, deviceId)
+            assertEquals(HOME_SHORTCUT, systemShortcut)
+            callbackCount++
+        }
+
+        // Notifying keyboard system shortcut triggered will notify the listener.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testAddingListenersRegistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+        assertNull(registeredListener)
+
+        // Adding the listener should register the callback with InputManagerService.
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        assertNotNull(registeredListener)
+
+        // Adding another listener should not register new internal listener.
+        val currListener = registeredListener
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+        assertEquals(currListener, registeredListener)
+    }
+
+    @Test
+    fun testRemovingListenersUnregistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+        // Only removing all listeners should remove the internal callback
+        inputManager.unregisterKeyboardSystemShortcutListener(callback1)
+        assertNotNull(registeredListener)
+        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+        assertNull(registeredListener)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ }
+        val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ }
+
+        // Add both keyboard system shortcut listeners
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+        // Notifying keyboard system shortcut triggered, should notify both the callbacks.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+        // Notifying keyboard system shortcut triggered, should still trigger callback1 but not
+        // callback2.
+        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
new file mode 100644
index 0000000..5a40a1c
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IKeyboardSystemShortcutListener
+import android.hardware.input.KeyboardSystemShortcut
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link KeyboardShortcutCallbackHandler}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardShortcutCallbackHandlerTests
+ */
+@Presubmit
+class KeyboardShortcutCallbackHandlerTests {
+
+    companion object {
+        val DEVICE_ID = 1
+        val HOME_SHORTCUT = KeyboardSystemShortcut(
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler
+    private lateinit var context: Context
+    private var lastShortcut: KeyboardSystemShortcut? = null
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler()
+    }
+
+    @Test
+    fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() {
+        val listener = KeyboardSystemShortcutListener()
+
+        // Register keyboard system shortcut listener
+        keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0)
+        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        assertEquals(
+            "Listener should get callback on keyboard system shortcut triggered",
+            HOME_SHORTCUT,
+            lastShortcut!!
+        )
+
+        // Unregister listener
+        lastShortcut = null
+        keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0)
+        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+        assertNull("Listener should not get callback after being unregistered", lastShortcut)
+    }
+
+    inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() {
+        override fun onKeyboardSystemShortcutTriggered(
+                deviceId: Int,
+                keycodes: IntArray,
+                modifierState: Int,
+                shortcut: Int
+        ) {
+            assertEquals(DEVICE_ID, deviceId)
+            lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/systemfeatures/OWNERS b/tools/systemfeatures/OWNERS
new file mode 100644
index 0000000..66c8506
--- /dev/null
+++ b/tools/systemfeatures/OWNERS
@@ -0,0 +1 @@
+include /PERFORMANCE_OWNERS
diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md
new file mode 100644
index 0000000..5836f81
--- /dev/null
+++ b/tools/systemfeatures/README.md
@@ -0,0 +1,11 @@
+# Build-time system feature support
+
+## Overview
+
+System features exposed from `PackageManager` are defined and aggregated as
+`<feature>` xml attributes across various partitions, and are currently queried
+at runtime through the framework. This directory contains tooling that will
+support *build-time* queries of select system features, enabling optimizations
+like code stripping and conditionally dependencies when so configured.
+
+### TODO(b/203143243): Expand readme after landing codegen.