Merge "Logging when virtual hal is used" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 22f736e..705a4df 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
+    ":android.server.app.flags-aconfig-java{.generated_srcjars}",
     ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
     ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
@@ -658,7 +659,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
-// CoreNetworking
+// Networking
+aconfig_declarations {
+    name: "com.android.net.flags-aconfig",
+    package: "com.android.net.flags",
+    srcs: ["core/java/android/net/flags.aconfig"],
+}
+
 java_aconfig_library {
     name: "com.android.net.flags-aconfig-java",
     aconfig_declarations: "com.android.net.flags-aconfig",
@@ -847,6 +854,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// App
+aconfig_declarations {
+    name: "android.server.app.flags-aconfig",
+    package: "android.server.app",
+    srcs: ["services/core/java/com/android/server/app/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.server.app.flags-aconfig-java",
+    aconfig_declarations: "android.server.app.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // WebView
 aconfig_declarations {
     name: "android.webkit.flags-aconfig",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3409838..117faa2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -126,6 +126,12 @@
           "exclude-annotation": "org.junit.Ignore"
         }
       ]
+    },
+    {
+      "name": "vts_treble_vintf_framework_test"
+    },
+    {
+      "name": "vts_treble_vintf_vendor_test"
     }
   ],
   "postsubmit-ravenwood": [
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index ad3e422..03891bb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -314,9 +314,15 @@
         if (mPackageStoppedState.contains(uid, packageName)) {
             return mPackageStoppedState.get(uid, packageName);
         }
-        final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
-        mPackageStoppedState.add(uid, packageName, isStopped);
-        return isStopped;
+
+        try {
+            final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+            mPackageStoppedState.add(uid, packageName, isStopped);
+            return isStopped;
+        } catch (IllegalArgumentException e) {
+            Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            return false;
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1c29982..8ddbf69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.UidObserver;
+import android.app.job.JobInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -772,18 +773,23 @@
         if (!jobStatus.shouldTreatAsExpeditedJob()) {
             // If quota is currently "free", then the job can run for the full amount of time,
             // regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
-            if (mService.isBatteryCharging()
-                    // The top and foreground cases here were added because apps in those states
-                    // aren't really restricted and the work could be something the user is
-                    // waiting for. Now that user-initiated jobs are a defined concept, we may
-                    // not need these exemptions as much. However, UIJs are currently limited
-                    // (as of UDC) to data transfer work. There may be other work that could
-                    // rely on this exception. Once we add more UIJ types, we can re-evaluate
-                    // the need for these exceptions.
-                    // TODO: re-evaluate the need for these exceptions
-                    || mTopAppCache.get(jobStatus.getSourceUid())
+            if (mService.isBatteryCharging()) {
+                return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+            }
+            // The top and foreground cases here were added because apps in those states
+            // aren't really restricted and the work could be something the user is
+            // waiting for. Now that user-initiated jobs are a defined concept, we may
+            // not need these exemptions as much. However, UIJs are currently limited
+            // (as of UDC) to data transfer work. There may be other work that could
+            // rely on this exception. Once we add more UIJ types, we can re-evaluate
+            // the need for these exceptions.
+            // TODO: re-evaluate the need for these exceptions
+            final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
-                    || isUidInForeground(jobStatus.getSourceUid())) {
+                    || isUidInForeground(jobStatus.getSourceUid());
+            final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH
+                    || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+            if (isInPrivilegedState && isJobImportant) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
             return getTimeUntilQuotaConsumedLocked(
@@ -2549,7 +2555,25 @@
          */
         @Override
         public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget();
+            // Skip posting a message to the handler for events we don't care about.
+            switch (event.getEventType()) {
+                case UsageEvents.Event.ACTIVITY_RESUMED:
+                case UsageEvents.Event.ACTIVITY_PAUSED:
+                case UsageEvents.Event.ACTIVITY_STOPPED:
+                case UsageEvents.Event.ACTIVITY_DESTROYED:
+                case UsageEvents.Event.USER_INTERACTION:
+                case UsageEvents.Event.CHOOSER_ACTION:
+                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                case UsageEvents.Event.NOTIFICATION_SEEN:
+                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                            .sendToTarget();
+                    break;
+                default:
+                    if (DEBUG) {
+                        Slog.d(TAG, "Dropping event " + event.getEventType());
+                    }
+                    break;
+            }
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index b8397d2..357e1396 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -316,8 +316,25 @@
                  */
                 @Override
                 public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
-                            .sendToTarget();
+                    // Skip posting a message to the handler for events we don't care about.
+                    switch (event.getEventType()) {
+                        case UsageEvents.Event.ACTIVITY_RESUMED:
+                        case UsageEvents.Event.ACTIVITY_PAUSED:
+                        case UsageEvents.Event.ACTIVITY_STOPPED:
+                        case UsageEvents.Event.ACTIVITY_DESTROYED:
+                        case UsageEvents.Event.USER_INTERACTION:
+                        case UsageEvents.Event.CHOOSER_ACTION:
+                        case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                        case UsageEvents.Event.NOTIFICATION_SEEN:
+                            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                                    .sendToTarget();
+                            break;
+                        default:
+                            if (DEBUG) {
+                                Slog.d(TAG, "Dropping event " + event.getEventType());
+                            }
+                            break;
+                    }
                 }
             };
 
diff --git a/api/Android.bp b/api/Android.bp
index 2b1cfcb..9029d25 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -31,12 +31,10 @@
         "blueprint",
         "soong",
         "soong-android",
-        "soong-bp2build",
         "soong-genrule",
         "soong-java",
     ],
     srcs: ["api.go"],
-    testSrcs: ["api_test.go"],
     pluginFor: ["soong_build"],
 }
 
diff --git a/api/api.go b/api/api.go
index 71b1e10..2668999 100644
--- a/api/api.go
+++ b/api/api.go
@@ -22,7 +22,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/bazel"
 	"android/soong/genrule"
 	"android/soong/java"
 )
@@ -65,7 +64,6 @@
 
 type CombinedApis struct {
 	android.ModuleBase
-	android.BazelModuleBase
 
 	properties CombinedApisProperties
 }
@@ -115,20 +113,6 @@
 	Previous_api        *string
 }
 
-type Bazel_module struct {
-	Label              *string
-	Bp2build_available *bool
-}
-type bazelProperties struct {
-	*Bazel_module
-}
-
-var bp2buildNotAvailable = bazelProperties{
-	&Bazel_module{
-		Bp2build_available: proptools.BoolPtr(false),
-	},
-}
-
 // Struct to pass parameters for the various merged [current|removed].txt file modules we create.
 type MergedTxtDefinition struct {
 	// "current.txt" or "removed.txt"
@@ -143,8 +127,6 @@
 	ModuleTag string
 	// public, system, module-lib or system-server
 	Scope string
-	// True if there is a bp2build definition for this module
-	Bp2buildDefined bool
 }
 
 func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) {
@@ -178,20 +160,7 @@
 		},
 	}
 	props.Visibility = []string{"//visibility:public"}
-	bazelProps := bazelProperties{
-		&Bazel_module{
-			Bp2build_available: proptools.BoolPtr(false),
-		},
-	}
-	if txt.Bp2buildDefined {
-		moduleDir := ctx.ModuleDir()
-		if moduleDir == android.Bp2BuildTopLevel {
-			moduleDir = ""
-		}
-		label := fmt.Sprintf("//%s:%s", moduleDir, moduleName)
-		bazelProps.Label = &label
-	}
-	ctx.CreateModule(genrule.GenRuleFactory, &props, &bazelProps)
+	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
 func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
@@ -221,7 +190,7 @@
 		props := fgProps{}
 		props.Name = proptools.StringPtr(i.name)
 		props.Srcs = createSrcs(i.modules, i.tag)
-		ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
+		ctx.CreateModule(android.FileGroupFactory, &props)
 	}
 }
 
@@ -315,7 +284,7 @@
 	props.Name = proptools.StringPtr("all-modules-public-stubs-source")
 	props.Srcs = createSrcs(modules, "{.public.stubs.source}")
 	props.Visibility = []string{"//frameworks/base"}
-	ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
+	ctx.CreateModule(android.FileGroupFactory, &props)
 }
 
 func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
@@ -323,43 +292,38 @@
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
 	distFilename := []string{"android.txt", "android-removed.txt"}
-	bp2BuildDefined := []bool{true, false}
 	for i, f := range []string{"current.txt", "removed.txt"} {
 		textFiles = append(textFiles, MergedTxtDefinition{
-			TxtFilename:     f,
-			DistFilename:    distFilename[i],
-			BaseTxt:         ":non-updatable-" + f,
-			Modules:         bootclasspath,
-			ModuleTag:       "{.public" + tagSuffix[i],
-			Scope:           "public",
-			Bp2buildDefined: bp2BuildDefined[i],
+			TxtFilename:  f,
+			DistFilename: distFilename[i],
+			BaseTxt:      ":non-updatable-" + f,
+			Modules:      bootclasspath,
+			ModuleTag:    "{.public" + tagSuffix[i],
+			Scope:        "public",
 		})
 		textFiles = append(textFiles, MergedTxtDefinition{
-			TxtFilename:     f,
-			DistFilename:    distFilename[i],
-			BaseTxt:         ":non-updatable-system-" + f,
-			Modules:         bootclasspath,
-			ModuleTag:       "{.system" + tagSuffix[i],
-			Scope:           "system",
-			Bp2buildDefined: bp2BuildDefined[i],
+			TxtFilename:  f,
+			DistFilename: distFilename[i],
+			BaseTxt:      ":non-updatable-system-" + f,
+			Modules:      bootclasspath,
+			ModuleTag:    "{.system" + tagSuffix[i],
+			Scope:        "system",
 		})
 		textFiles = append(textFiles, MergedTxtDefinition{
-			TxtFilename:     f,
-			DistFilename:    distFilename[i],
-			BaseTxt:         ":non-updatable-module-lib-" + f,
-			Modules:         bootclasspath,
-			ModuleTag:       "{.module-lib" + tagSuffix[i],
-			Scope:           "module-lib",
-			Bp2buildDefined: bp2BuildDefined[i],
+			TxtFilename:  f,
+			DistFilename: distFilename[i],
+			BaseTxt:      ":non-updatable-module-lib-" + f,
+			Modules:      bootclasspath,
+			ModuleTag:    "{.module-lib" + tagSuffix[i],
+			Scope:        "module-lib",
 		})
 		textFiles = append(textFiles, MergedTxtDefinition{
-			TxtFilename:     f,
-			DistFilename:    distFilename[i],
-			BaseTxt:         ":non-updatable-system-server-" + f,
-			Modules:         system_server_classpath,
-			ModuleTag:       "{.system-server" + tagSuffix[i],
-			Scope:           "system-server",
-			Bp2buildDefined: bp2BuildDefined[i],
+			TxtFilename:  f,
+			DistFilename: distFilename[i],
+			BaseTxt:      ":non-updatable-system-server-" + f,
+			Modules:      system_server_classpath,
+			ModuleTag:    "{.system-server" + tagSuffix[i],
+			Scope:        "system-server",
 		})
 	}
 	for _, txt := range textFiles {
@@ -446,55 +410,9 @@
 	module.AddProperties(&module.properties)
 	android.InitAndroidModule(module)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
-	android.InitBazelModule(module)
 	return module
 }
 
-type bazelCombinedApisAttributes struct {
-	Scope bazel.StringAttribute
-	Base  bazel.LabelAttribute
-	Deps  bazel.LabelListAttribute
-}
-
-// combined_apis bp2build converter
-func (a *CombinedApis) ConvertWithBp2build(ctx android.Bp2buildMutatorContext) {
-	basePrefix := "non-updatable"
-	scopeToSuffix := map[string]string{
-		"public":        "-current.txt",
-		"system":        "-system-current.txt",
-		"module-lib":    "-module-lib-current.txt",
-		"system-server": "-system-server-current.txt",
-	}
-
-	for scopeName, suffix := range scopeToSuffix {
-		name := a.Name() + suffix
-
-		var scope bazel.StringAttribute
-		scope.SetValue(scopeName)
-
-		var base bazel.LabelAttribute
-		base.SetValue(android.BazelLabelForModuleDepSingle(ctx, basePrefix+suffix))
-
-		var deps bazel.LabelListAttribute
-		classpath := a.properties.Bootclasspath
-		if scopeName == "system-server" {
-			classpath = a.properties.System_server_classpath
-		}
-		deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, classpath))
-
-		attrs := bazelCombinedApisAttributes{
-			Scope: scope,
-			Base:  base,
-			Deps:  deps,
-		}
-		props := bazel.BazelTargetModuleProperties{
-			Rule_class:        "merged_txts",
-			Bzl_load_location: "//build/bazel/rules/java:merged_txts.bzl",
-		}
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, &attrs)
-	}
-}
-
 // Various utility methods below.
 
 // Creates an array of ":<m><tag>" for each m in <modules>.
diff --git a/api/api_test.go b/api/api_test.go
deleted file mode 100644
index 70f2162..0000000
--- a/api/api_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package api
-
-import (
-	"testing"
-
-	"android/soong/android"
-	"android/soong/bp2build"
-	"android/soong/java"
-)
-
-func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
-	t.Helper()
-	(&tc).ModuleTypeUnderTest = "combined_apis"
-	(&tc).ModuleTypeUnderTestFactory = combinedApisModuleFactory
-	bp2build.RunBp2BuildTestCase(t, registrationCtxFunc, tc)
-}
-
-func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) {
-	t.Helper()
-	runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {
-		ctx.RegisterModuleType("java_defaults", java.DefaultsFactory)
-		ctx.RegisterModuleType("java_sdk_library", java.SdkLibraryFactory)
-		ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	})
-}
-
-func TestCombinedApisGeneral(t *testing.T) {
-	runCombinedApisTestCase(t, bp2build.Bp2buildTestCase{
-		Description: "combined_apis, general case",
-		Blueprint: `combined_apis {
-    name: "foo",
-    bootclasspath: ["bcp"],
-    system_server_classpath: ["ssc"],
-}
-
-java_sdk_library {
-		name: "bcp",
-		srcs: ["a.java", "b.java"],
-		shared_library: false,
-}
-java_sdk_library {
-		name: "ssc",
-		srcs: ["a.java", "b.java"],
-		shared_library: false,
-}
-filegroup {
-    name: "non-updatable-current.txt",
-    srcs: ["current.txt"],
-}
-filegroup {
-    name: "non-updatable-system-current.txt",
-    srcs: ["system-current.txt"],
-}
-filegroup {
-    name: "non-updatable-module-lib-current.txt",
-    srcs: ["system-removed.txt"],
-}
-filegroup {
-    name: "non-updatable-system-server-current.txt",
-    srcs: ["system-lint-baseline.txt"],
-}
-`,
-		Filesystem: map[string]string{
-			"a/Android.bp": `
-			java_defaults {
-				name: "android.jar_defaults",
-			}
-			`,
-			"api/current.txt":        "",
-			"api/removed.txt":        "",
-			"api/system-current.txt": "",
-			"api/system-removed.txt": "",
-			"api/test-current.txt":   "",
-			"api/test-removed.txt":   "",
-		},
-		StubbedBuildDefinitions:    []string{"bcp", "ssc", "non-updatable-current.txt", "non-updatable-system-current.txt", "non-updatable-module-lib-current.txt", "non-updatable-system-server-current.txt"},
-		ExpectedHandcraftedModules: []string{"foo-current.txt", "foo-system-current.txt", "foo-module-lib-current.txt", "foo-system-server-current.txt"},
-		ExpectedBazelTargets: []string{
-			bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{
-				"scope": `"public"`,
-				"base":  `":non-updatable-current.txt"`,
-				"deps":  `[":bcp"]`,
-			}),
-			bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{
-				"scope": `"system"`,
-				"base":  `":non-updatable-system-current.txt"`,
-				"deps":  `[":bcp"]`,
-			}),
-			bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{
-				"scope": `"module-lib"`,
-				"base":  `":non-updatable-module-lib-current.txt"`,
-				"deps":  `[":bcp"]`,
-			}),
-			bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{
-				"scope": `"system-server"`,
-				"base":  `":non-updatable-system-server-current.txt"`,
-				"deps":  `[":ssc"]`,
-			}),
-		},
-	})
-}
diff --git a/api/go.mod b/api/go.mod
index f8bb1c0..445a6f4 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -6,7 +6,5 @@
 	android/soong v0.0.0
 	github.com/google/blueprint v0.0.0
 	google.golang.org/protobuf v0.0.0
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0
-	prebuilts/bazel/common/proto/build v0.0.0
 	go.starlark.net v0.0.0
 )
diff --git a/api/go.work b/api/go.work
index aa2d2b1..edd002e 100644
--- a/api/go.work
+++ b/api/go.work
@@ -13,7 +13,5 @@
 	google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
 	github.com/google/blueprint v0.0.0 => ../../../build/blueprint
 	github.com/google/go-cmp v0.0.0 => ../../../external/go-cmp
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0 => ../../../prebuilts/bazel/common/proto/analysis_v2
-	prebuilts/bazel/common/proto/build v0.0.0 => ../../../prebuilts/bazel/common/proto/build
 	go.starlark.net v0.0.0 => ../../../external/starlark-go
 )
diff --git a/core/api/current.txt b/core/api/current.txt
index 8e5c4c4..83e3fab 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4468,7 +4468,7 @@
     method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
     method @CallSuper protected void onRestart();
     method protected void onRestoreInstanceState(@NonNull android.os.Bundle);
     method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
@@ -4510,7 +4510,7 @@
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
     method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
@@ -4558,7 +4558,7 @@
     method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean shouldDockBigOverlays();
     method public boolean shouldShowRequestPermissionRationale(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
     method @Deprecated public final void showDialog(int);
@@ -9839,7 +9839,7 @@
     method public int describeContents();
     method public void enforceCallingUid();
     method @Nullable public String getAttributionTag();
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
     method public int getPid();
@@ -9855,7 +9855,7 @@
     ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
@@ -39071,6 +39071,7 @@
 
   public class NetworkSecurityPolicy {
     method public static android.security.NetworkSecurityPolicy getInstance();
+    method @FlaggedApi("android.security.certificate_transparency_configuration") public boolean isCertificateTransparencyVerificationRequired(@NonNull String);
     method public boolean isCleartextTrafficPermitted();
     method public boolean isCleartextTrafficPermitted(String);
   }
@@ -44721,12 +44722,12 @@
     field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
   }
 
-  public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
-    ctor public PhoneNumberFormattingTextWatcher();
-    ctor public PhoneNumberFormattingTextWatcher(String);
-    method public void afterTextChanged(android.text.Editable);
-    method public void beforeTextChanged(CharSequence, int, int, int);
-    method public void onTextChanged(CharSequence, int, int, int);
+  @Deprecated public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
+    ctor @Deprecated public PhoneNumberFormattingTextWatcher();
+    ctor @Deprecated @WorkerThread public PhoneNumberFormattingTextWatcher(String);
+    method @Deprecated public void afterTextChanged(android.text.Editable);
+    method @Deprecated public void beforeTextChanged(CharSequence, int, int, int);
+    method @Deprecated public void onTextChanged(CharSequence, int, int, int);
   }
 
   public class PhoneNumberUtils {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 46d7d76..5ecc0b6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -656,6 +656,7 @@
     field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
     field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
     field public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
+    field @FlaggedApi("android.view.contentprotection.flags.rapid_clear_notifications_by_listener_app_op_enabled") public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener";
     field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
     field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
     field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
@@ -4009,7 +4010,7 @@
     field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
     field public static final int DELETE_KEEP_DATA = 1; // 0x1
     field public static final int DELETE_SUCCEEDED = 1; // 0x1
-    field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
+    field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -4090,7 +4091,7 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
-    field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
+    field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
     field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -4115,7 +4116,7 @@
 
   public static interface PackageManager.OnPermissionsChangedListener {
     method public void onPermissionsChanged(int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String);
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
@@ -10927,13 +10928,13 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
     method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
     method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -14522,6 +14523,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearRadioPowerOffForReason(int);
     method public void dial(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity();
+    method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableCellularIdentifierDisclosureNotifications(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableModemForSlot(int, boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean);
@@ -14594,6 +14596,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCellularIdentifierDisclosureNotificationEnabled();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eaabda8..a3cd3dc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2,6 +2,7 @@
 package android {
 
   public static final class Manifest.permission {
+    field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
@@ -99,6 +100,8 @@
 
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     method @NonNull public android.content.ComponentName getComponentName();
+    method @FlaggedApi("android.view.accessibility.motion_event_observing") public int getObservedMotionEventSources();
+    method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int);
   }
 
 }
@@ -903,7 +906,7 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
-    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
+    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8ad6ea2..fc342fa 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -20,6 +20,7 @@
 import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
 import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -53,6 +54,7 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.Flags;
 
 import com.android.internal.R;
 import com.android.internal.compat.IPlatformCompat;
@@ -630,7 +632,8 @@
             InputDevice.SOURCE_TOUCH_NAVIGATION,
             InputDevice.SOURCE_ROTARY_ENCODER,
             InputDevice.SOURCE_JOYSTICK,
-            InputDevice.SOURCE_SENSOR
+            InputDevice.SOURCE_SENSOR,
+            InputDevice.SOURCE_TOUCHSCREEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface MotionEventSources {}
@@ -642,6 +645,8 @@
     @MotionEventSources
     private int mMotionEventSources = 0;
 
+    private int mObservedMotionEventSources = 0;
+
     /**
      * Creates a new instance.
      */
@@ -817,6 +822,9 @@
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
         mMotionEventSources = other.mMotionEventSources;
+        if (Flags.motionEventObserving()) {
+            setObservedMotionEventSources(other.mObservedMotionEventSources);
+        }
         // NOTE: Ensure that only properties that are safe to be modified by the service itself
         // are included here (regardless of hidden setters, etc.).
     }
@@ -1024,16 +1032,75 @@
      */
     public void setMotionEventSources(@MotionEventSources int motionEventSources) {
         mMotionEventSources = motionEventSources;
+        mObservedMotionEventSources = 0;
+    }
+
+    /**
+     * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service
+     * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested
+     * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will
+     * be sent to the rest of the input pipeline without being consumed by accessibility services.
+     * This service will still be able to see them.
+     *
+     * <p><strong>Note:</strong> you will need to call this function every time you call {@link
+     * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of
+     * observed motion event sources for this service.
+     *
+     * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+     * that complicate bitwise flag removal operations. To remove a specific source you should
+     * rebuild the entire value using bitwise OR operations on the individual source constants.
+     *
+     * <p>Including an {@link android.view.InputDevice} source that does not send {@link
+     * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive
+     * any events from that source.
+     *
+     * <p><strong>Note:</strong> Calling this function with a source that has not been listened to
+     * using {@link #setMotionEventSources(int)} will throw an exception.
+     *
+     * @see AccessibilityService#onMotionEvent
+     * @see #MotionEventSources
+     * @see #setMotionEventSources(int)
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+    @TestApi
+    public void setObservedMotionEventSources(int observedMotionEventSources) {
+        // Confirm that any sources requested here have already been requested for listening.
+        if ((observedMotionEventSources & ~mMotionEventSources) != 0) {
+            String message =
+                    String.format(
+                            "Requested motion event sources for listening = 0x%x but requested"
+                                    + " motion event sources for observing = 0x%x.",
+                            mMotionEventSources, observedMotionEventSources);
+            throw new IllegalArgumentException(message);
+        }
+        mObservedMotionEventSources = observedMotionEventSources;
+    }
+
+    /**
+     * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+     * service wants to observe generic {@link android.view.MotionEvent}s from if it has already
+     * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these
+     * sources will be sent to the rest of the input pipeline without being consumed by
+     * accessibility services. This service will still be able to see them.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+    @MotionEventSources
+    @TestApi
+    public int getObservedMotionEventSources() {
+        return mObservedMotionEventSources;
     }
 
     /**
      * The localized summary of the accessibility service.
-     * <p>
-     *    <strong>Statically set from
-     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
-     * </p>
-     * @return The localized summary if available, and {@code null} if a summary
-     * has not been provided.
+     *
+     * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
+     * meta-data}.</strong>
+     *
+     * @return The localized summary if available, and {@code null} if a summary has not been
+     *     provided.
      */
     public CharSequence loadSummary(PackageManager packageManager) {
         if (mSummaryResId == 0) {
@@ -1260,6 +1327,7 @@
         parcel.writeString(mTileServiceName);
         parcel.writeInt(mIntroResId);
         parcel.writeInt(mMotionEventSources);
+        parcel.writeInt(mObservedMotionEventSources);
     }
 
     private void initFromParcel(Parcel parcel) {
@@ -1285,6 +1353,8 @@
         mTileServiceName = parcel.readString();
         mIntroResId = parcel.readInt();
         mMotionEventSources = parcel.readInt();
+        // use the setter here because it throws an exception for invalid values.
+        setObservedMotionEventSources(parcel.readInt());
     }
 
     @Override
diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java
index 7700b33..87304c8 100644
--- a/core/java/android/accessibilityservice/AccessibilityTrace.java
+++ b/core/java/android/accessibilityservice/AccessibilityTrace.java
@@ -36,7 +36,7 @@
             "IAccessibilityInteractionConnectionCallback";
     String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
     String NAME_MAGNIFICATION_CONNECTION = "IMagnificationConnection";
-    String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback";
+    String NAME_MAGNIFICATION_CONNECTION_CALLBACK = "IMagnificationConnectionCallback";
     String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
     String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
     String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks";
@@ -59,7 +59,7 @@
     long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
     long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
     long FLAGS_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
-    long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
+    long FLAGS_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
     long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
     long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
     long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L;
@@ -100,8 +100,8 @@
             new AbstractMap.SimpleEntry<String, Long>(
                     NAME_MAGNIFICATION_CONNECTION, FLAGS_MAGNIFICATION_CONNECTION),
             new AbstractMap.SimpleEntry<String, Long>(
-                    NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK),
+                    NAME_MAGNIFICATION_CONNECTION_CALLBACK,
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK),
             new AbstractMap.SimpleEntry<String, Long>(
                     NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL),
             new AbstractMap.SimpleEntry<String, Long>(
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cb08dad..4a9fa9e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5572,7 +5572,7 @@
      * @see #shouldShowRequestPermissionRationale
      * @see Context#DEVICE_ID_DEFAULT
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public final void requestPermissions(@NonNull String[] permissions, int requestCode,
             int deviceId) {
         if (requestCode < 0) {
@@ -5645,7 +5645,7 @@
      *
      * @see #requestPermissions
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
             @NonNull int[] grantResults, int deviceId) {
         onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -5678,7 +5678,7 @@
      * @see #requestPermissions
      * @see #onRequestPermissionsResult
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
@@ -6884,8 +6884,8 @@
      * application package was involved.
      *
      * <p>If called while inside the handling of {@link #onNewIntent}, this function will
-     * return the referrer that submitted that new intent to the activity.  Otherwise, it
-     * always returns the referrer of the original Intent.</p>
+     * return the referrer that submitted that new intent to the activity only after
+     * {@link #setIntent(Intent)} is called with the provided intent.</p>
      *
      * <p>Note that this is <em>not</em> a security feature -- you can not trust the
      * referrer information, applications can spoof it.</p>
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ec43184..4b24b1f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,6 +18,7 @@
 
 import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER;
 import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
+import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED;
 
 import static java.lang.Long.max;
 
@@ -1508,6 +1509,7 @@
      */
     public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
             AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+
     /**
      * Indicate that the user has enabled or disabled mobile data
      * @hide
@@ -1529,9 +1531,17 @@
      */
     public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
 
+    /**
+     * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+     *
+     * @hide
+     */
+    public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
+            AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 142;
+    public static final int _NUM_OP = 143;
 
     /**
      * All app ops represented as strings.
@@ -1680,6 +1690,7 @@
             OPSTR_MEDIA_ROUTING_CONTROL,
             OPSTR_ENABLE_MOBILE_DATA_BY_USER,
             OPSTR_RESERVED_FOR_TESTING,
+            OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
     })
     public @interface AppOpString {}
 
@@ -2330,6 +2341,7 @@
     @FlaggedApi(FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED)
     public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY =
             "android:create_accessibility_overlay";
+
     /**
      * Indicate that the user has enabled or disabled mobile data
      * @hide
@@ -2350,6 +2362,16 @@
     public static final String OPSTR_RESERVED_FOR_TESTING =
             "android:reserved_for_testing";
 
+    /**
+     * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED)
+    public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
+            "android:rapid_clear_notifications_by_listener";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2909,6 +2931,10 @@
                 "ENABLE_MOBILE_DATA_BY_USER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_RESERVED_FOR_TESTING, OPSTR_RESERVED_FOR_TESTING,
                 "OP_RESERVED_FOR_TESTING").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+                OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+                "RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
+                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 9a818e4..bfec9430 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -52,4 +52,6 @@
     void removeGameModeListener(IGameModeListener gameModeListener);
     void addGameStateListener(IGameStateListener gameStateListener);
     void removeGameStateListener(IGameStateListener gameStateListener);
+    @EnforcePermission("MANAGE_GAME_MODE")
+    void toggleGameDefaultFrameRate(boolean isEnabled);
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 90a2659..4a6349b1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9381,7 +9381,7 @@
     @Deprecated
     @SystemApi
     @RequiresPermission(MANAGE_DEVICE_ADMINS)
-    public boolean setActiveProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName)
+    public boolean setActiveProfileOwner(@NonNull ComponentName admin, String ownerName)
             throws IllegalArgumentException {
         throwIfParentInstance("setActiveProfileOwner");
         if (mService != null) {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6e45147..4cf9fca 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -16,6 +16,8 @@
 
 package android.appwidget;
 
+import static android.appwidget.flags.Flags.remoteAdapterConversion;
+
 import android.annotation.BroadcastBehavior;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -566,11 +568,9 @@
     private void tryAdapterConversion(
             FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action,
             RemoteViews original, String failureMsg) {
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+        if (remoteAdapterConversion()
                 && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (original != null && original.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
+                        || (original != null && original.hasLegacyLists()))) {
             final RemoteViews viewsCopy = new RemoteViews(original);
             Runnable updateWidgetWithTask = () -> {
                 try {
@@ -587,13 +587,12 @@
             }
 
             updateWidgetWithTask.run();
-            return;
-        }
-
-        try {
-            action.acceptOrThrow(original);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+        } else {
+            try {
+                action.acceptOrThrow(original);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -838,22 +837,20 @@
             return;
         }
 
-        if (!RemoteViews.isAdapterConversionEnabled()) {
+        if (remoteAdapterConversion()) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                mHasPostedLegacyLists = true;
+                createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(
+                        appWidgetIds, viewId));
+            } else {
+                notifyCollectionWidgetChange(appWidgetIds, viewId);
+            }
+        } else {
             try {
                 mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
-
-            return;
-        }
-
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            mHasPostedLegacyLists = true;
-            createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds,
-                    viewId));
-        } else {
-            notifyCollectionWidgetChange(appWidgetIds, viewId);
         }
     }
 
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 6a735a4..c95b864 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -6,3 +6,10 @@
   description: "Enable support for generated previews in AppWidgetManager"
   bug: "306546610"
 }
+
+flag {
+  name: "remote_adapter_conversion"
+  namespace: "app_widgets"
+  description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
+  bug: "245950570"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index e4a03c5..d5b5f40 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -37,6 +37,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -709,7 +710,9 @@
             IntentSender intentSender = mService
                     .requestNotificationAccess(component, mContext.getUserId())
                     .getIntentSender();
-            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+            mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                    ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (IntentSender.SendIntentException e) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b2074a6..a1357c9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -173,7 +173,7 @@
 
     /** @hide */
     @TestApi
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
             @Nullable String[] renouncedPermissions,
@@ -539,7 +539,7 @@
      * <p>
      * This device ID is used for permissions checking during attribution source validation.
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public int getDeviceId() {
         return mAttributionSourceState.deviceId;
     }
@@ -727,7 +727,7 @@
          *
          * @return the builder
          */
-        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         public @NonNull Builder setDeviceId(int deviceId) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x12;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 23a5d4d..7af0be3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2888,7 +2888,7 @@
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      * <p>
-     * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+     * Starting in Android V, an extra timestamp
      * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
      * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
      * </p>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f865a36..a5d16f2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -731,7 +731,7 @@
          * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId()
          * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT
          */
-        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) {
             Objects.requireNonNull(persistentDeviceId);
             if (Objects.equals(persistentDeviceId,
@@ -1225,12 +1225,10 @@
     public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
 
     /**
-     * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
+     * Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
      *
      * @hide
      */
-    @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation
-    @Deprecated
     @SystemApi
     public static final int MATCH_CLONE_PROFILE = 0x20000000;
 
@@ -4893,7 +4891,7 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID =
             "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
 
@@ -6302,6 +6300,11 @@
     /**
      * Check whether a particular package has been granted a particular
      * permission.
+     * <p>
+     * <strong>Note: </strong>This API returns the underlying permission state
+     * as-is and is mostly intended for permission managing system apps. To
+     * perform an access check for a certain app, please use the
+     * {@link Context#checkPermission} APIs instead.
      *
      * @param permName The name of the permission you are checking for.
      * @param packageName The name of the package you are checking against.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 6c6b33b..57025c2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -49,3 +49,10 @@
     description: "Add support to unlock the private space using biometrics"
     bug: "312184187"
 }
+
+flag {
+    name: "support_autolock_for_private_space"
+    namespace: "profile_experiences"
+    description: "Add support to lock private space automatically after a time period"
+    bug: "303201022"
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e3a5520..3ab889d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3317,7 +3317,7 @@
      * stream configurations are the same as for applications targeting SDK version older than
      * 31.</p>
      * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a>
      * for additional mandatory stream configurations on a per-capability basis.</p>
      * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p>
      * <ul>
@@ -3436,11 +3436,12 @@
      * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL }
      * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>.
+     * based on specific device level and capabilities.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * As per documentation, the stream combinations with given PREVIEW, RECORD and
@@ -3469,11 +3470,12 @@
     /**
      * <p>An array of mandatory concurrent stream combinations.
      * This is an app-readable conversion of the concurrent mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a>
+     * for each device which has its Id present in the set returned by
      * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
@@ -3578,7 +3580,7 @@
      * <p>If a camera device supports multi-resolution output streams for a particular format, for
      * each of its mandatory stream combinations, the camera device will support using a
      * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for additional details.</p>
      * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }.
      * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with
@@ -3587,7 +3589,7 @@
      * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution
      * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported
      * multi-resolution output stream formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for details about the additional mandatory stream combinations in this case.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
@@ -3700,11 +3702,12 @@
      * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set
      * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This is an app-readable conversion of the maximum resolution mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3727,11 +3730,12 @@
      * 10-bit output capability
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * This is an app-readable conversion of the 10 bit output mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3752,11 +3756,12 @@
      * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
      * This is an app-readable conversion of the preview stabilization mandatory stream
      * combination
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a>
+     * for each device which supports {@code PREVIEW_STABILIZATION}
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * The mandatory stream combination array will be {@code null} in case the device does not
@@ -3829,8 +3834,8 @@
      * <p>The guaranteed stream combinations related to stream use case for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability is documented in the camera device
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream
-     * combinations.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>.
+     * The application is strongly recommended to use one of the guaranteed stream combinations.
      * If the application creates a session with a stream combination not in the guaranteed
      * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
      * the camera device may ignore some stream use cases due to hardware constraints
@@ -3866,11 +3871,13 @@
     /**
      * <p>An array of mandatory stream combinations with stream use cases.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a>
+     * with each stream's use case being set.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a>
+     * for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability.
      * The mandatory stream combination array will be {@code null} in case the device doesn't
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 8196bf5..20b0932 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -40,7 +40,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -722,6 +721,7 @@
                 switch(format) {
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
+                    case ImageFormat.JPEG_R:
                         break;
                     default:
                         throw new IllegalArgumentException("Unsupported format: " + format);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 765a8f7..93fbe8a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,8 +1216,7 @@
      * <ul>
      * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
      * <li>All mandatory stream combinations for this specific capability as per
-     *   documentation
-     *   {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li>
+     *   <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
      * <li>In case the device is not able to capture some combination of supported
      *   standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
      *   then those constraints must be listed in
@@ -1256,8 +1255,8 @@
      * </ul>
      * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
      * lists all of the supported stream use cases.</p>
-     * <p>Refer to
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations }
+     * <p>Refer to the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>
      * for the mandatory stream combinations involving stream use cases, which can also be
      * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p>
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -1756,9 +1755,9 @@
     /**
      * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or
      * better.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic
      * support for color image capture. The only exception is that the device may
      * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth
@@ -1784,9 +1783,9 @@
 
     /**
      * <p>This camera device is capable of supporting advanced imaging applications.</p>
-     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>A <code>FULL</code> device will support below capabilities:</p>
      * <ul>
      * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
@@ -1814,9 +1813,9 @@
 
     /**
      * <p>This camera device is running in backward compatibility mode.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
-     * documentation are supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a>
+     * in the documentation are supported.</p>
      * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
      * post-processing, arbitrary cropping regions, and has relaxed performance constraints.
      * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -1839,9 +1838,9 @@
      * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
      * FULL-level capabilities.</p>
      * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
-     * <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>The following additional capabilities are guaranteed to be supported:</p>
      * <ul>
      * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
index 0dbf29d..8a18a0d 100644
--- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java
+++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,6 +34,7 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
 
 import java.nio.NioUtils;
 import java.util.ArrayList;
@@ -165,6 +167,104 @@
     }
 
     /**
+     * <p>
+     * Create a new multi-resolution reader based on a group of camera stream properties returned
+     * by a camera device, and the desired format, maximum buffer capacity and consumer usage flag.
+     * </p>
+     * <p>
+     * The valid size and formats depend on the camera characteristics.
+     * {@code MultiResolutionImageReader} for an image format is supported by the camera device if
+     * the format is in the supported multi-resolution output stream formats returned by
+     * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
+     * If the image format is supported, the {@code MultiResolutionImageReader} object can be
+     * created with the {@code streams} objects returned by
+     * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}.
+     * </p>
+     * <p>
+     * The {@code maxImages} parameter determines the maximum number of
+     * {@link Image} objects that can be acquired from each of the {@code ImageReader}
+     * within the {@code MultiResolutionImageReader}. However, requesting more buffers will
+     * use up more memory, so it is important to use only the minimum number necessary. The
+     * application is strongly recommended to acquire no more than {@code maxImages} images
+     * from all of the internal ImageReader objects combined. By keeping track of the number of
+     * acquired images for the MultiResolutionImageReader, the application doesn't need to do the
+     * bookkeeping for each internal ImageReader returned from {@link
+     * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback.
+     * </p>
+     * <p>
+     * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex
+     * configuration sequence. Instead of passing the same surface to OutputConfiguration and
+     * CaptureRequest, the
+     * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput}
+     * call needs to be used to create the OutputConfigurations for session creation, and then
+     * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for
+     * CaptureRequest}.
+     * </p>
+     * @param streams The group of multi-resolution stream info, which is used to create
+     *            a multi-resolution reader containing a number of ImageReader objects. Each
+     *            ImageReader object represents a multi-resolution stream in the group.
+     * @param format The format of the Image that this multi-resolution reader will produce.
+     *            This must be one of the {@link android.graphics.ImageFormat} or
+     *            {@link android.graphics.PixelFormat} constants. Note that not all formats are
+     *            supported, like ImageFormat.NV21. The supported multi-resolution
+     *            reader format can be queried by {@link
+     *            android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
+     * @param maxImages The maximum number of images the user will want to
+     *            access simultaneously. This should be as small as possible to
+     *            limit memory use. Once maxImages images are obtained by the
+     *            user from any given internal ImageReader, one of them has to be released before
+     *            a new Image will become available for access through the ImageReader's
+     *            {@link ImageReader#acquireLatestImage()} or
+     *            {@link ImageReader#acquireNextImage()}. Must be greater than 0.
+     * @param usage The intended usage of the images produced by the internal ImageReader. See the usages
+     *              on {@link HardwareBuffer} for a list of valid usage bits. See also
+     *              {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking
+     *              if a combination is supported. If it's not supported this will throw
+     *              an {@link IllegalArgumentException}.
+     * @see Image
+     * @see
+     * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP
+     * @see
+     * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_CONFIG)
+    public MultiResolutionImageReader(
+            @NonNull Collection<MultiResolutionStreamInfo> streams,
+            @Format             int format,
+            @IntRange(from = 1) int maxImages,
+            @Usage              long usage) {
+        mFormat = format;
+        mMaxImages = maxImages;
+
+        if (streams == null || streams.size() <= 1) {
+            throw new IllegalArgumentException(
+                "The streams info collection must contain at least 2 entries");
+        }
+        if (mMaxImages < 1) {
+            throw new IllegalArgumentException(
+                "Maximum outstanding image count must be at least 1");
+        }
+
+        if (format == ImageFormat.NV21) {
+            throw new IllegalArgumentException(
+                    "NV21 format is not supported");
+        }
+
+        int numImageReaders = streams.size();
+        mReaders = new ImageReader[numImageReaders];
+        mStreamInfo = new MultiResolutionStreamInfo[numImageReaders];
+        int index = 0;
+        for (MultiResolutionStreamInfo streamInfo : streams) {
+            mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(),
+                    streamInfo.getHeight(), format, maxImages, usage);
+            mStreamInfo[index] = streamInfo;
+            index++;
+        }
+    }
+
+    /**
      * Set onImageAvailableListener callback.
      *
      * <p>This function sets the onImageAvailableListener for all the internal
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8e49c4c..134a510 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -403,14 +403,13 @@
 
     /**
      * Virtual display flag: Indicates that the display should support system decorations. Virtual
-     * displays without this flag shouldn't show home, IME or any other system decorations.
+     * displays without this flag shouldn't show home, navigation bar or wallpaper.
      * <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p>
      *
      * @see #createVirtualDisplay
      * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     @TestApi
     public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
 
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 7388b5b..9e09759 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -447,7 +447,8 @@
          * Sets whether this display supports showing home activities and wallpaper.
          *
          * <p>If set to {@code true}, then the home activity relevant to this display will be
-         * automatically launched upon the display creation.</p>
+         * automatically launched upon the display creation. If unset or set to {@code false}, the
+         * display will not host any activities upon creation.</p>
          *
          * <p>Note: setting to {@code true} requires the display to be trusted. If the display is
          * not trusted, this property is ignored.</p>
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 33960c0..145dbf2 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -16,6 +16,8 @@
 
 package android.hardware.input;
 
+import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+
 import android.Manifest;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
@@ -58,6 +60,11 @@
      */
     public static final float DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = .8f;
 
+    /**
+     * The maximum allowed Accessibility bounce keys threshold.
+     * @hide
+     */
+    public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000;
 
     private InputSettings() {
     }
@@ -328,4 +335,70 @@
                        .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
                || InputProperties.force_enable_stylus_pointer_icon().orElse(false);
     }
+
+    /**
+     * Whether Accessibility bounce keys is enabled.
+     *
+     * <p>
+     * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities,
+     * that allows the user to configure the device to ignore rapid, repeated keypresses of the
+     * same key.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isAccessibilityBounceKeysEnabled(@NonNull Context context) {
+        return getAccessibilityBounceKeysThreshold(context) != 0;
+    }
+
+    /**
+     * Get Accessibility bounce keys threshold duration in milliseconds.
+     *
+     * <p>
+     * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities,
+     * that allows the user to configure the device to ignore rapid, repeated keypresses of the
+     * same key.
+     * </p>
+     *
+     * @hide
+     */
+    public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) {
+        if (!keyboardA11yBounceKeysFlag()) {
+            return 0;
+        }
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Set Accessibility bounce keys threshold duration in milliseconds.
+     * @param thresholdTimeMillis time duration for which a key down will be ignored after a
+     *                            previous key up for the same key on the same device between 0 and
+     *                            {@link MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS}
+     *
+     * <p>
+     * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities,
+     * that allows the user to configure the device to ignore rapid, repeated keypresses of the
+     * same key.
+     * </p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setAccessibilityBounceKeysThreshold(@NonNull Context context,
+            int thresholdTimeMillis) {
+        if (!keyboardA11yBounceKeysFlag()) {
+            return;
+        }
+        if (thresholdTimeMillis < 0
+                || thresholdTimeMillis > MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS) {
+            throw new IllegalArgumentException(
+                    "Provided Bounce keys threshold should be in range [0, "
+                            + MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS + "]");
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, thresholdTimeMillis,
+                UserHandle.USER_CURRENT);
+    }
+
 }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 97c45a7..362fe78 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -17,6 +17,12 @@
     bug: "294546335"
 }
 
+flag {
+    namespace: "input_native"
+    name: "keyboard_a11y_bounce_keys_flag"
+    description: "Controls if the bounce keys accessibility feature for physical keyboard is available to the user"
+    bug: "294546335"
+}
 
 flag {
     namespace: "input_native"
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
new file mode 100644
index 0000000..0ad1804
--- /dev/null
+++ b/core/java/android/net/flags.aconfig
@@ -0,0 +1,50 @@
+package: "com.android.net.flags"
+
+flag {
+  name: "track_multiple_network_activities"
+  namespace: "android_core_networking"
+  description: "NetworkActivityTracker tracks multiple networks including non default networks"
+  bug: "267870186"
+}
+
+flag {
+  name: "forbidden_capability"
+  namespace: "android_core_networking"
+  description: "This flag controls the forbidden capability API"
+  bug: "302997505"
+}
+
+flag {
+  name: "set_data_saver_via_cm"
+  namespace: "android_core_networking"
+  description: "Set data saver through ConnectivityManager API"
+  bug: "297836825"
+}
+
+flag {
+  name: "support_is_uid_networking_blocked"
+  namespace: "android_core_networking"
+  description: "This flag controls whether isUidNetworkingBlocked is supported"
+  bug: "297836825"
+}
+
+flag {
+  name: "basic_background_restrictions_enabled"
+  namespace: "android_core_networking"
+  description: "Block network access for apps in a low importance background state"
+  bug: "304347838"
+}
+
+flag {
+  name: "register_nsd_offload_engine"
+  namespace: "android_core_networking"
+  description: "The flag controls the access for registerOffloadEngine API in NsdManager"
+  bug: "294777050"
+}
+
+flag {
+  name: "ipsec_transform_state"
+  namespace: "android_core_networking_ipsec"
+  description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
+  bug: "308011229"
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 6a83cee..05a1abea 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BatteryConsumer {
 
     private static final String TAG = "BatteryConsumer";
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 16ffaef..10a8022 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1657,6 +1657,7 @@
      */
     public abstract CpuScalingPolicies getCpuScalingPolicies();
 
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class HistoryTag {
         public static final int HISTORY_TAG_POOL_OVERFLOW = -1;
 
@@ -1713,6 +1714,7 @@
      * Optional detailed information that can go into a history step.  This is typically
      * generated each time the battery level changes.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class HistoryStepDetails {
         // Time (in 1/100 second) spent in user space and the kernel since the last step.
         public int userTime;
@@ -1797,6 +1799,7 @@
     /**
      * An extension to the history item describing a proc state change for a UID.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class ProcessStateChange {
         public int uid;
         public @BatteryConsumer.ProcessState int processState;
@@ -1850,6 +1853,7 @@
     /**
      * Battery history record.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class HistoryItem {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
         public HistoryItem next;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 0ccc485..02e40cf 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -396,6 +396,7 @@
      * This is Test API which will be used to override output of isDirectlyHandlingTransactionNative
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void setIsDirectlyHandlingTransactionOverride(boolean isInTransaction) {
         sIsHandlingBinderTransaction = isInTransaction;
     }
@@ -1068,6 +1069,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable String getTransactionName(int transactionCode) {
         return null;
     }
@@ -1076,6 +1078,7 @@
      * @hide
      */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodKeep
     public final @Nullable String getTransactionTraceName(int transactionCode) {
         final boolean isInterfaceUserDefined = getMaxTransactionId() == 0;
         if (mTransactionTraceNames == null) {
@@ -1113,6 +1116,7 @@
         return transactionTraceName;
     }
 
+    @android.ravenwood.annotation.RavenwoodKeep
     private @NonNull String getSimpleDescriptor() {
         String descriptor = mDescriptor;
         if (descriptor == null) {
@@ -1132,6 +1136,7 @@
      * @return The highest user-defined transaction id of all transactions.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public int getMaxTransactionId() {
         return 0;
     }
diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java
index 88760b0..17cf692 100644
--- a/core/java/android/os/Broadcaster.java
+++ b/core/java/android/os/Broadcaster.java
@@ -19,6 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 
 /** @hide */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Broadcaster
 {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java
index 857aaf5..dc243a5 100644
--- a/core/java/android/os/BundleMerger.java
+++ b/core/java/android/os/BundleMerger.java
@@ -48,6 +48,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BundleMerger implements Parcelable {
     private static final String TAG = "BundleMerger";
 
diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java
index a13eaa6..b5ed53b 100644
--- a/core/java/android/os/ConditionVariable.java
+++ b/core/java/android/os/ConditionVariable.java
@@ -29,6 +29,7 @@
  * This class uses itself as the object to wait on, so if you wait()
  * or notify() on a ConditionVariable, the results are undefined.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ConditionVariable
 {
     private volatile boolean mCondition;
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index a1d2dcc..2aab2b4 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -81,10 +81,8 @@
 
     /**
      * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
-     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and later, you
-     * must hold the {@link android.Manifest.permission#READ_DROPBOX_DATA} permission
-     * in order to receive this broadcast. For apps targeting Android versions lower
-     * than {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, you must hold
+     * For apps targeting 35 and later, For apps targeting Android versions lower
+     * than 35, you must hold
      * {@link android.Manifest.permission#READ_LOGS}.
      * This broadcast can be rate limited for low priority entries
      *
@@ -385,11 +383,8 @@
     /**
      * Gets the next entry from the drop box <em>after</em> the specified time.
      * You must always call {@link Entry#close()} on the return value!
-     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is
-     * required for apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
-     * and later. {@link android.Manifest.permission#READ_LOGS} permission is
-     * required for apps targeting Android versions lower than
-     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+     * {@link android.Manifest.permission#READ_LOGS} permission is
+     * required for apps targeting Android versions lower than 35.
      *
      * @param tag of entry to look for, null for all tags
      * @param msec time of the last entry seen
diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java
index df99074..cbc4272 100644
--- a/core/java/android/os/PackageTagsList.java
+++ b/core/java/android/os/PackageTagsList.java
@@ -41,6 +41,7 @@
  */
 @TestApi
 @Immutable
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PackageTagsList implements Parcelable {
 
     // an empty set value matches any attribution tag (ie, wildcard)
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f2930fe..8e860c3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -465,9 +465,7 @@
     private static native byte[] nativeMarshall(long nativePtr);
     private static native void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
-    @RavenwoodThrow
     private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
-    @RavenwoodThrow
     private static native boolean nativeCompareDataInRange(
             long ptrA, int offsetA, long ptrB, int offsetB, int length);
     private static native void nativeAppendFrom(
diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
index 3d8a550..e514d63 100644
--- a/core/java/android/os/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -35,6 +35,7 @@
  * @param <T> the type of the value with an associated timestamp
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class TimestampedValue<T> implements Parcelable {
     private final long mReferenceTimeMillis;
     @Nullable
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index bc80c8b..6d4e284 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -23,6 +23,7 @@
  * Currently the public representation of what a work source is not
  * defined; this is an opaque container.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class WorkSource implements Parcelable {
     static final String TAG = "WorkSource";
     static final boolean DEBUG = false;
@@ -141,11 +142,17 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isChainedBatteryAttributionEnabled(Context context) {
         return Settings.Global.getInt(context.getContentResolver(),
                 Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
     }
 
+    /** @hide */
+    public static boolean isChainedBatteryAttributionEnabled$ravenwood(Context context) {
+        return false;
+    }
+
     /**
      * Returns the number of uids in this work source.
      * @hide
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index bf22dcc..6941857 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -1,6 +1,6 @@
 # Bug component: 95221
 
-# Please assign new bugs to android-storage-triage@, not to individual people
+# PLEASE ASSIGN NEW BUGS TO android-storage-triage@, NOT TO INDIVIDUAL PEOPLE
 
 # Android Storage Team
 alukin@google.com
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 9fe2599..5a12760 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -318,7 +318,7 @@
      *                 a virtual device. See {@link Context#DEVICE_ID_DEFAULT}
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onOneTimePermissionSessionTimeout(@NonNull String packageName,
             int deviceId) {
         onOneTimePermissionSessionTimeout(packageName);
@@ -393,7 +393,7 @@
      * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection)
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
             @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) {
         onRevokeSelfPermissionsOnKill(packageName, permissions, callback);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index cbeb821..d09c229 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,7 +1,7 @@
 package: "android.permission.flags"
 
 flag {
-  name: "device_aware_permission_apis"
+  name: "device_aware_permission_apis_enabled"
   is_fixed_read_only: true
   namespace: "permissions"
   description: "enable device aware permission APIs"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9c27f19..2cc56d8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7791,6 +7791,16 @@
         public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
 
         /**
+         * Whether to enable bounce keys for Physical Keyboard accessibility.
+         *
+         * If set to non-zero value, any key press on physical keyboard within the provided
+         * threshold duration (in milliseconds) of the same key, will be ignored.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
+
+        /**
          * Whether stylus button presses are disabled. This is a boolean that
          * determines if stylus buttons are ignored.
          *
@@ -8291,6 +8301,17 @@
         public static final String ACCESSIBILITY_BUTTON_TARGETS = "accessibility_button_targets";
 
         /**
+         * Setting specifying the accessibility services, accessibility shortcut targets,
+         * or features to be toggled via a tile in the quick settings panel.
+         *
+         * <p> This is a colon-separated string list which contains the flattened
+         * {@link ComponentName} and the class name of a system class implementing a supported
+         * accessibility feature.
+         * @hide
+         */
+        public static final String ACCESSIBILITY_QS_TARGETS = "accessibility_qs_targets";
+
+        /**
          * The system class name of magnification controller which is a target to be toggled via
          * accessibility shortcut or accessibility button.
          *
@@ -12126,6 +12147,7 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
+            CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
         }
 
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index 0c4eeda..e679d20 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -16,6 +16,8 @@
 
 package android.security;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.security.net.config.ApplicationConfig;
@@ -26,9 +28,6 @@
  *
  * <p>Network stacks/components should honor this policy to make it possible to centrally control
  * the relevant aspects of network security behavior.
- *
- * <p>The policy currently consists of a single flag: whether cleartext network traffic is
- * permitted. See {@link #isCleartextTrafficPermitted()}.
  */
 public class NetworkSecurityPolicy {
 
@@ -94,6 +93,22 @@
     }
 
     /**
+     * Returns {@code true} if Certificate Transparency information is required to be verified by
+     * the client in TLS connections to {@code hostname}.
+     *
+     * <p>See RFC6962 section 3.3 for more details.
+     *
+     * @param hostname hostname to check whether certificate transparency verification is required
+     * @return {@code true} if certificate transparency verification is required and {@code false}
+     *     otherwise
+     */
+    @FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_CONFIGURATION)
+    public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) {
+        return libcore.net.NetworkSecurityPolicy.getInstance()
+                .isCertificateTransparencyVerificationRequired(hostname);
+    }
+
+    /**
      * Handle an update to the system or user certificate stores.
      * @hide
      */
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 28ef70b..b56bef3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.security"
 
 flag {
+    name: "certificate_transparency_configuration"
+    namespace: "network_security"
+    description: "Enable certificate transparency setting in the network security config"
+    bug: "28746284"
+}
+
+flag {
     name: "fsverity_api"
     namespace: "hardware_backed_security"
     description: "Feature flag for fs-verity API"
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 801eceb..4cc870b 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -16,10 +16,15 @@
 
 package android.security.net.config;
 
+import static android.security.Flags.certificateTransparencyConfiguration;
+
+import android.annotation.NonNull;
 import android.util.Pair;
+
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
+
 import javax.net.ssl.X509TrustManager;
 
 /**
@@ -147,6 +152,22 @@
         return getConfigForHostname(hostname).isCleartextTrafficPermitted();
     }
 
+    /**
+     * Returns {@code true} if Certificate Transparency information is required to be verified by
+     * the client in TLS connections to {@code hostname}.
+     *
+     * <p>See RFC6962 section 3.3 for more details.
+     *
+     * @param hostname hostname to check whether certificate transparency verification is required
+     * @return {@code true} if certificate transparency verification is required and {@code false}
+     *     otherwise
+     */
+    public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) {
+        return certificateTransparencyConfiguration()
+                ? getConfigForHostname(hostname).isCertificateTransparencyVerificationRequired()
+                : NetworkSecurityConfig.DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
+    }
+
     public void handleTrustStorageUpdate() {
         synchronized(mLock) {
             // If the config is uninitialized then there is no work to be done to handle an update,
diff --git a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
index a708f5b..801b32b 100644
--- a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
+++ b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
@@ -40,6 +40,6 @@
 
     @Override
     public boolean isCertificateTransparencyVerificationRequired(String hostname) {
-        return false;
+        return mConfig.isCertificateTransparencyVerificationRequired(hostname);
     }
 }
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 00872fb..129ae63 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -38,9 +38,12 @@
     public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
     /** @hide */
     public static final boolean DEFAULT_HSTS_ENFORCED = false;
+    /** @hide */
+    public static final boolean DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED = false;
 
     private final boolean mCleartextTrafficPermitted;
     private final boolean mHstsEnforced;
+    private final boolean mCertificateTransparencyVerificationRequired;
     private final PinSet mPins;
     private final List<CertificatesEntryRef> mCertificatesEntryRefs;
     private Set<TrustAnchor> mAnchors;
@@ -48,10 +51,15 @@
     private NetworkSecurityTrustManager mTrustManager;
     private final Object mTrustManagerLock = new Object();
 
-    private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
-            PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
+    private NetworkSecurityConfig(
+            boolean cleartextTrafficPermitted,
+            boolean hstsEnforced,
+            boolean certificateTransparencyVerificationRequired,
+            PinSet pins,
+            List<CertificatesEntryRef> certificatesEntryRefs) {
         mCleartextTrafficPermitted = cleartextTrafficPermitted;
         mHstsEnforced = hstsEnforced;
+        mCertificateTransparencyVerificationRequired = certificateTransparencyVerificationRequired;
         mPins = pins;
         mCertificatesEntryRefs = certificatesEntryRefs;
         // Sort the certificates entry refs so that all entries that override pins come before
@@ -104,6 +112,11 @@
         return mHstsEnforced;
     }
 
+    // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides.
+    public boolean isCertificateTransparencyVerificationRequired() {
+        return mCertificateTransparencyVerificationRequired;
+    }
+
     public PinSet getPins() {
         return mPins;
     }
@@ -208,6 +221,9 @@
         private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED;
         private boolean mCleartextTrafficPermittedSet = false;
         private boolean mHstsEnforcedSet = false;
+        private boolean mCertificateTransparencyVerificationRequired =
+                DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
+        private boolean mCertificateTransparencyVerificationRequiredSet = false;
         private Builder mParentBuilder;
 
         /**
@@ -313,12 +329,35 @@
             return mCertificatesEntryRefs;
         }
 
+        Builder setCertificateTransparencyVerificationRequired(boolean required) {
+            mCertificateTransparencyVerificationRequired = required;
+            mCertificateTransparencyVerificationRequiredSet = true;
+            return this;
+        }
+
+        private boolean getCertificateTransparencyVerificationRequired() {
+            if (mCertificateTransparencyVerificationRequiredSet) {
+                return mCertificateTransparencyVerificationRequired;
+            }
+            if (mParentBuilder != null) {
+                return mParentBuilder.getCertificateTransparencyVerificationRequired();
+            }
+            return DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
+        }
+
         public NetworkSecurityConfig build() {
             boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
             boolean hstsEnforced = getEffectiveHstsEnforced();
+            boolean certificateTransparencyVerificationRequired =
+                    getCertificateTransparencyVerificationRequired();
             PinSet pinSet = getEffectivePinSet();
             List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();
-            return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);
+            return new NetworkSecurityConfig(
+                    cleartextPermitted,
+                    hstsEnforced,
+                    certificateTransparencyVerificationRequired,
+                    pinSet,
+                    entryRefs);
         }
     }
 }
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index 311a8d2..b1c1479 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -171,6 +171,11 @@
         return new Domain(domain, includeSubdomains);
     }
 
+    private boolean parseCertificateTransparency(XmlResourceParser parser)
+            throws IOException, XmlPullParserException, ParserException {
+        return parser.getAttributeBooleanValue(null, "enabled", false);
+    }
+
     private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser,
             boolean defaultOverridePins)
             throws IOException, XmlPullParserException, ParserException {
@@ -226,7 +231,6 @@
         boolean seenPinSet = false;
         boolean seenTrustAnchors = false;
         boolean defaultOverridePins = configType == CONFIG_DEBUG;
-        String configName = parser.getName();
         int outerDepth = parser.getDepth();
         // Add this builder now so that this builder occurs before any of its children. This
         // makes the final build pass easier.
@@ -279,6 +283,15 @@
                             "Nested domain-config not allowed in " + getConfigString(configType));
                 }
                 builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType));
+            } else if ("certificateTransparency".equals(tagName)) {
+                if (configType != CONFIG_BASE && configType != CONFIG_DOMAIN) {
+                    throw new ParserException(
+                            parser,
+                            "certificateTransparency not allowed in "
+                                    + getConfigString(configType));
+                }
+                builder.setCertificateTransparencyVerificationRequired(
+                        parseCertificateTransparency(parser));
             } else {
                 XmlUtils.skipCurrentTag(parser);
             }
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
index 234ff4d..5194cdd 100644
--- a/core/java/android/service/notification/DeviceEffectsApplier.java
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -16,6 +16,8 @@
 
 package android.service.notification;
 
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
 /**
  * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
  * make sense for the current platform.
@@ -33,6 +35,13 @@
      *
      * <p>This will be called whenever the set of consolidated effects changes (normally through
      * the activation or deactivation of zen rules).
+     *
+     * @param effects The effects that should be active and inactive.
+     * @param source The origin of the change. Because the application of specific effects can be
+     *               disruptive (e.g. lead to Activity recreation), that operation can in some
+     *               cases be deferred (e.g. until screen off). However, if the effects are
+     *               changing as a result of an explicit user action, then it makes sense to
+     *               apply them immediately regardless.
      */
-    void apply(ZenDeviceEffects effects);
+    void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source);
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index f6128ea..a5b087c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -26,6 +26,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -61,6 +62,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -79,7 +82,66 @@
  * @hide
  */
 public class ZenModeConfig implements Parcelable {
-    private static String TAG = "ZenModeConfig";
+    private static final String TAG = "ZenModeConfig";
+
+    /**
+     * The {@link ZenModeConfig} is being updated because of an unknown reason.
+     */
+    public static final int UPDATE_ORIGIN_UNKNOWN = 0;
+
+    /**
+     * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from
+     * storage, on device boot).
+     */
+    public static final int UPDATE_ORIGIN_INIT = 1;
+
+    /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */
+    public static final int UPDATE_ORIGIN_INIT_USER = 2;
+
+    /** The {@link ZenModeConfig} is being updated because of a user action, for example:
+     * <ul>
+     *     <li>{@link NotificationManager#setAutomaticZenRuleState} with a
+     *     {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
+     *     <li>Adding, updating, or removing a rule from Settings.</li>
+     *     <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g.
+     *     Quick Settings).</li>
+     * </ul>
+     */
+    public static final int UPDATE_ORIGIN_USER = 3;
+
+    /**
+     * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of
+     * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on
+     * a previously set schedule).
+     */
+    public static final int UPDATE_ORIGIN_APP = 4;
+
+    /**
+     * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only
+     * includes cases where the call is coming from the System/SystemUI but the change is not due to
+     * a user action (e.g. automatically activating a schedule-based rule). If the change is a
+     * result of a user action (e.g. activating a rule by tapping on its QS tile) then
+     * {@link #UPDATE_ORIGIN_USER} is used instead.
+     */
+    public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5;
+
+    /**
+     * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration
+     * is being restored from a backup.
+     */
+    public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6;
+
+    @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = {
+            UPDATE_ORIGIN_UNKNOWN,
+            UPDATE_ORIGIN_INIT,
+            UPDATE_ORIGIN_INIT_USER,
+            UPDATE_ORIGIN_USER,
+            UPDATE_ORIGIN_APP,
+            UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+            UPDATE_ORIGIN_RESTORE_BACKUP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigChangeOrigin {}
 
     public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
     public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 07dd882..1908c64c 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -276,15 +276,14 @@
     /**
      * Display flag: Indicates that the display should show system decorations.
      * <p>
-     * This flag identifies secondary displays that should show system decorations, such as status
-     * bar, navigation bar, home activity or IME.
+     * This flag identifies secondary displays that should show system decorations, such as
+     * navigation bar, home activity or wallpaper.
      * </p>
      * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
      *
      * @see #getFlags()
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6;
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 659db5f..5cbb42e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -826,11 +826,19 @@
      * The resolved pointer icon type requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
      */
+    // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
     @Nullable
     private Integer mPointerIconType = null;
     private PointerIcon mCustomPointerIcon = null;
 
     /**
+     * The resolved pointer icon requested by this window.
+     * A null value indicates the resolved pointer icon has not yet been calculated.
+     */
+    @Nullable
+    private PointerIcon mResolvedPointerIcon = null;
+
+    /**
      * see {@link #playSoundEffect(int)}
      */
     AudioManager mAudioManager;
@@ -7410,12 +7418,14 @@
                 // Other apps or the window manager may change the icon type outside of
                 // this app, therefore the icon type has to be reset on enter/exit event.
                 mPointerIconType = null;
+                mResolvedPointerIcon = null;
             }
 
             if (action != MotionEvent.ACTION_HOVER_EXIT) {
                 // Resolve the pointer icon
                 if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
                     mPointerIconType = null;
+                    mResolvedPointerIcon = null;
                 }
             }
 
@@ -7470,6 +7480,7 @@
 
     private void resetPointerIcon(MotionEvent event) {
         mPointerIconType = null;
+        mResolvedPointerIcon = null;
         updatePointerIcon(event);
     }
 
@@ -7507,6 +7518,21 @@
             pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
         }
 
+        if (enablePointerChoreographer()) {
+            if (pointerIcon == null) {
+                pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+            }
+            if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
+                return true;
+            }
+            mResolvedPointerIcon = pointerIcon;
+
+            InputManagerGlobal.getInstance()
+                    .setPointerIcon(pointerIcon, event.getDisplayId(),
+                            event.getDeviceId(), event.getPointerId(0), getInputToken());
+            return true;
+        }
+
         final int pointerType = (pointerIcon != null) ?
                 pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
 
@@ -7514,34 +7540,18 @@
             mPointerIconType = pointerType;
             mCustomPointerIcon = null;
             if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                if (enablePointerChoreographer()) {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType),
-                                    event.getDisplayId(), event.getDeviceId(),
-                                    event.getPointerId(pointerIndex), getInputToken());
-                } else {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIconType(pointerType);
-                }
+                InputManagerGlobal
+                        .getInstance()
+                        .setPointerIconType(pointerType);
                 return true;
             }
         }
         if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
                 !pointerIcon.equals(mCustomPointerIcon)) {
             mCustomPointerIcon = pointerIcon;
-            if (enablePointerChoreographer()) {
-                InputManagerGlobal
-                        .getInstance()
-                        .setPointerIcon(mCustomPointerIcon,
-                                event.getDisplayId(), event.getDeviceId(),
-                                event.getPointerId(pointerIndex), getInputToken());
-            } else {
-                InputManagerGlobal
-                        .getInstance()
-                        .setCustomPointerIcon(mCustomPointerIcon);
-            }
+            InputManagerGlobal
+                    .getInstance()
+                    .setCustomPointerIcon(mCustomPointerIcon);
         }
         return true;
     }
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index c9526fd..3b444c4 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -75,7 +75,7 @@
     private boolean mWindowShown;
 
     // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw
-    private String mLastDispatchOnPreDrawCanceledReason;
+    private StringBuilder mLastDispatchOnPreDrawCanceledReason;
 
     private boolean mAlive = true;
 
@@ -1173,9 +1173,15 @@
                 int count = access.size();
                 for (int i = 0; i < count; i++) {
                     final OnPreDrawListener preDrawListener = access.get(i);
-                    cancelDraw |= !(preDrawListener.onPreDraw());
-                    if (cancelDraw) {
-                        mLastDispatchOnPreDrawCanceledReason = preDrawListener.getClass().getName();
+                    final boolean listenerCanceledDraw = !(preDrawListener.onPreDraw());
+                    cancelDraw |= listenerCanceledDraw;
+                    if (listenerCanceledDraw) {
+                        final String className = preDrawListener.getClass().getName();
+                        if (mLastDispatchOnPreDrawCanceledReason == null) {
+                            mLastDispatchOnPreDrawCanceledReason = new StringBuilder(className);
+                        } else {
+                            mLastDispatchOnPreDrawCanceledReason.append("|").append(className);
+                        }
                     }
                 }
             } finally {
@@ -1191,7 +1197,10 @@
      * @hide
      */
     final String getLastDispatchOnPreDrawCanceledReason() {
-        return mLastDispatchOnPreDrawCanceledReason;
+        if (mLastDispatchOnPreDrawCanceledReason != null) {
+            return mLastDispatchOnPreDrawCanceledReason.toString();
+        }
+        return null;
     }
 
     /**
diff --git a/core/java/android/view/accessibility/IMagnificationConnection.aidl b/core/java/android/view/accessibility/IMagnificationConnection.aidl
index a5e8aaf..aae51ab 100644
--- a/core/java/android/view/accessibility/IMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IMagnificationConnection.aidl
@@ -18,7 +18,7 @@
 
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 
 /**
@@ -110,11 +110,11 @@
     void removeMagnificationSettingsPanel(int displayId);
 
     /**
-     * Sets {@link IWindowMagnificationConnectionCallback} to receive the request or the callback.
+     * Sets {@link IMagnificationConnectionCallback} to receive the request or the callback.
      *
      * @param callback the interface to be called.
      */
-    void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+    void setConnectionCallback(in IMagnificationConnectionCallback callback);
 
     /**
      * Notify System UI the magnification scale on the specified display for userId is changed.
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IMagnificationConnectionCallback.aidl
similarity index 97%
rename from core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
rename to core/java/android/view/accessibility/IMagnificationConnectionCallback.aidl
index 21b4334..0ba61b1 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IMagnificationConnectionCallback.aidl
@@ -24,7 +24,7 @@
  *
  * @hide
  */
- oneway interface IWindowMagnificationConnectionCallback {
+ oneway interface IMagnificationConnectionCallback {
 
     /**
      * Called when the bounds of the mirrow window is changed.
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e057660..0cc19fb 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -59,6 +59,13 @@
 }
 
 flag {
+    name: "motion_event_observing"
+    namespace: "accessibility"
+    description: "Allows accessibility services to intercept but not consume motion events from specified sources."
+    bug: "297595990"
+}
+
+flag {
     namespace: "accessibility"
     name: "granular_scrolling"
     description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 6bc2a13..bb49679 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -38,6 +38,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityOptions;
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.ViewNodeBuilder;
 import android.app.assist.AssistStructure.ViewNodeParcelable;
@@ -4370,7 +4371,11 @@
             if (afm != null) {
                 afm.post(() -> {
                     try {
-                        afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
+                        Bundle options = ActivityOptions.makeBasic()
+                                .setPendingIntentBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle();
+                        afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0, options);
                     } catch (IntentSender.SendIntentException e) {
                         Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
                     }
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index f3dc33c..2a3008a 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -27,3 +27,10 @@
     description: "If true, an appop is logged on creation of accessibility overlays."
     bug: "289081465"
 }
+
+flag {
+    name: "rapid_clear_notifications_by_listener_app_op_enabled"
+    namespace: "content_protection"
+    description: "If true, an appop is logged when a notification is rapidly cleared by a notification listener."
+    bug: "289080543"
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index da31348..8ad10af 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.appwidget.flags.Flags.remoteAdapterConversion;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
 import android.annotation.AttrRes;
@@ -36,7 +37,6 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.app.Application;
 import android.app.LoadedApk;
 import android.app.PendingIntent;
@@ -108,7 +108,6 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
 import com.android.internal.R;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.IRemoteViewsFactory;
 
@@ -4950,21 +4949,11 @@
      */
     @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
-        if (isAdapterConversionEnabled()) {
+        if (remoteAdapterConversion()) {
             addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
-            return;
+        } else {
+            addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
         }
-        addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
-    }
-
-    /**
-     * @hide
-     * @return True if the remote adapter conversion is enabled
-     */
-    public static boolean isAdapterConversionEnabled() {
-        return AppGlobals.getIntCoreSetting(
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
     }
 
     /**
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 31a3ebd..07beb11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -16,7 +16,7 @@
 
 flag {
     name: "defer_display_updates"
-    namespace: "window_manager"
+    namespace: "windowing_frontend"
     description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
     bug: "259220649"
     is_fixed_read_only: true
@@ -44,11 +44,18 @@
   bug: "294925498"
 }
 
-
 flag {
     name: "wallpaper_offset_async"
     namespace: "windowing_frontend"
     description: "Do not synchronise the wallpaper offset"
     bug: "293248754"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "predictive_back_system_animations"
+    namespace: "systemui"
+    description: "Predictive back for system animations"
+    bug: "309545085"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index e494346..bd806bf 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -519,24 +519,6 @@
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
 
     /**
-     * (boolean) Whether to enable the adapter conversion in RemoteViews
-     */
-    public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
-            "CursorControlFeature__remoteviews_adapter_conversion";
-
-    /**
-     * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
-     */
-    public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
-            "systemui__remoteviews_adapter_conversion";
-
-    /**
-     * Default value for whether the adapter conversion is enabled or not. This is set for
-     * RemoteViews and should not be a common practice.
-     */
-    public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
-
-    /**
      * (boolean) Whether the task manager should show a stop button if the app is allowlisted
      * by the user.
      */
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 7d78f29..0be9804 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -72,6 +72,7 @@
  * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
  * locks on BatteryStatsImpl object.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BatteryStatsHistory {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistory";
@@ -259,6 +260,7 @@
      * until the first change occurs.
      */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class TraceDelegate {
         // Note: certain tests currently run as platform_app which is not allowed
         // to set debug system properties. To ensure that system properties are set
@@ -391,10 +393,18 @@
     public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
             MonotonicClock monotonicClock) {
+        this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock,
+                new TraceDelegate());
+    }
+
+    @VisibleForTesting
+    public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate traceDelegate) {
         mMaxHistoryFiles = maxHistoryFiles;
         mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
-        mTracer = new TraceDelegate();
+        mTracer = traceDelegate;
         mClock = clock;
         mMonotonicClock = monotonicClock;
 
@@ -2096,6 +2106,7 @@
      * fewer bytes.  It is a bit more expensive than just writing the long into the parcel,
      * but at scale saves a lot of storage and allows recording of longer battery history.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class VarintParceler {
         /**
          * Writes an array of longs into Parcel using the varint format, see
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 6bd5898..2dffe15 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -28,6 +28,7 @@
 /**
  * An iterator for {@link BatteryStats.HistoryItem}'s.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
         AutoCloseable {
     private static final boolean DEBUG = false;
diff --git a/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java b/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java
index 7701761..00043cf 100644
--- a/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java
+++ b/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java
@@ -33,6 +33,7 @@
  * A watcher which makes stats on the incoming binder transaction, if the amount of some type of
  * transactions exceeds the threshold, the listener will be notified.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BinderCallHeavyHitterWatcher {
     private static final String TAG = "BinderCallHeavyHitterWatcher";
 
diff --git a/core/java/com/android/internal/os/BinderDeathDispatcher.java b/core/java/com/android/internal/os/BinderDeathDispatcher.java
index 8ca6241..e7abe2a 100644
--- a/core/java/com/android/internal/os/BinderDeathDispatcher.java
+++ b/core/java/com/android/internal/os/BinderDeathDispatcher.java
@@ -36,6 +36,7 @@
  *
  * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BinderDeathDispatcher<T extends IInterface> {
     private static final String TAG = "BinderDeathDispatcher";
 
diff --git a/core/java/com/android/internal/os/BinderLatencyBuckets.java b/core/java/com/android/internal/os/BinderLatencyBuckets.java
index d7d2d6a..5679bc7 100644
--- a/core/java/com/android/internal/os/BinderLatencyBuckets.java
+++ b/core/java/com/android/internal/os/BinderLatencyBuckets.java
@@ -26,6 +26,7 @@
  * Generates the bucket thresholds (with a custom logarithmic scale) for a histogram to store
  * latency samples in.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BinderLatencyBuckets {
     private static final String TAG = "BinderLatencyBuckets";
     private final int[] mBuckets;
diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java
index 9cc4a35..66f91e1 100644
--- a/core/java/com/android/internal/os/BinderfsStatsReader.java
+++ b/core/java/com/android/internal/os/BinderfsStatsReader.java
@@ -43,6 +43,7 @@
  * free async space 520192
  * ...
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BinderfsStatsReader {
     private final String mPath;
 
diff --git a/core/java/com/android/internal/os/CachedDeviceState.java b/core/java/com/android/internal/os/CachedDeviceState.java
index 334cca3..ac92f86 100644
--- a/core/java/com/android/internal/os/CachedDeviceState.java
+++ b/core/java/com/android/internal/os/CachedDeviceState.java
@@ -30,6 +30,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CachedDeviceState {
     private volatile boolean mScreenInteractive;
     private volatile boolean mCharging;
diff --git a/core/java/com/android/internal/os/Clock.java b/core/java/com/android/internal/os/Clock.java
index 45007c4..c2403d1 100644
--- a/core/java/com/android/internal/os/Clock.java
+++ b/core/java/com/android/internal/os/Clock.java
@@ -21,6 +21,7 @@
 /**
  * A wrapper for SystemClock, intended for mocking in unit tests.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class Clock {
     /** Elapsed Realtime, see SystemClock.elapsedRealtime() */
     public long elapsedRealtime() {
diff --git a/core/java/com/android/internal/os/CpuScalingPolicies.java b/core/java/com/android/internal/os/CpuScalingPolicies.java
index 6dbe8ab..f61cf97 100644
--- a/core/java/com/android/internal/os/CpuScalingPolicies.java
+++ b/core/java/com/android/internal/os/CpuScalingPolicies.java
@@ -27,6 +27,7 @@
  * CPU scaling policies: the policy IDs and corresponding supported scaling for those
  * policies.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CpuScalingPolicies {
     private final SparseArray<int[]> mCpusByPolicy;
     private final SparseArray<int[]> mFreqsByPolicy;
diff --git a/core/java/com/android/internal/os/CpuScalingPolicyReader.java b/core/java/com/android/internal/os/CpuScalingPolicyReader.java
index c96089a..0d272fd 100644
--- a/core/java/com/android/internal/os/CpuScalingPolicyReader.java
+++ b/core/java/com/android/internal/os/CpuScalingPolicyReader.java
@@ -40,6 +40,7 @@
  * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
  * #policy-interface-in-sysfs">Policy Interface in sysfs</a>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CpuScalingPolicyReader {
     private static final String TAG = "CpuScalingPolicyReader";
     private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq";
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 0843741..5b6d1b6 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -244,7 +244,8 @@
     }
 
     /** Set the UID predicate for {@link #getProcessCpuUsage} */
-    void setUidPredicate(Predicate<Integer> uidPredicate) {
+    @VisibleForTesting
+    public void setUidPredicate(Predicate<Integer> uidPredicate) {
         mUidPredicate = uidPredicate;
     }
 
diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java
index d27874c..4bf92bb 100644
--- a/core/java/com/android/internal/os/LoggingPrintStream.java
+++ b/core/java/com/android/internal/os/LoggingPrintStream.java
@@ -36,6 +36,7 @@
  * {@hide}
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class LoggingPrintStream extends PrintStream {
 
     private final StringBuilder builder = new StringBuilder();
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 1f44b33..ed943cb 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -55,15 +55,20 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host")
 public final class LongArrayMultiStateCounter implements Parcelable {
 
     /**
      * Container for a native equivalent of a long[].
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
+    @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+            "com.android.hoststubgen.nativesubstitution"
+            + ".LongArrayMultiStateCounter_host$LongArrayContainer_host")
     public static class LongArrayContainer {
-        private static final NativeAllocationRegistry sRegistry =
-                NativeAllocationRegistry.createMalloced(
-                        LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+        private static NativeAllocationRegistry sRegistry;
 
         // Visible to other objects in this package so that it can be passed to @CriticalNative
         // methods.
@@ -73,9 +78,26 @@
         public LongArrayContainer(int length) {
             mLength = length;
             mNativeObject = native_init(length);
+            registerNativeAllocation();
+        }
+
+        @android.ravenwood.annotation.RavenwoodReplace
+        private void registerNativeAllocation() {
+            if (sRegistry == null) {
+                synchronized (LongArrayMultiStateCounter.class) {
+                    if (sRegistry == null) {
+                        sRegistry = NativeAllocationRegistry.createMalloced(
+                                LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+                    }
+                }
+            }
             sRegistry.registerNativeAllocation(this, mNativeObject);
         }
 
+        private void registerNativeAllocation$ravenwood() {
+            // No-op under ravenwood
+        }
+
         /**
          * Copies the supplied values into the underlying native array.
          */
@@ -124,19 +146,17 @@
         private static native long native_getReleaseFunc();
 
         @FastNative
-        private native void native_setValues(long nativeObject, long[] array);
+        private static native void native_setValues(long nativeObject, long[] array);
 
         @FastNative
-        private native void native_getValues(long nativeObject, long[] array);
+        private static native void native_getValues(long nativeObject, long[] array);
 
         @FastNative
-        private native boolean native_combineValues(long nativeObject, long[] array,
+        private static native boolean native_combineValues(long nativeObject, long[] array,
                 int[] indexMap);
     }
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                    LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+    private static volatile NativeAllocationRegistry sRegistry;
     private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
             new AtomicReference<>();
 
@@ -152,12 +172,30 @@
         mStateCount = stateCount;
         mLength = arrayLength;
         mNativeObject = native_init(stateCount, arrayLength);
+        registerNativeAllocation();
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private void registerNativeAllocation() {
+        if (sRegistry == null) {
+            synchronized (LongArrayMultiStateCounter.class) {
+                if (sRegistry == null) {
+                    sRegistry = NativeAllocationRegistry.createMalloced(
+                            LongArrayMultiStateCounter.class.getClassLoader(),
+                            native_getReleaseFunc());
+                }
+            }
+        }
         sRegistry.registerNativeAllocation(this, mNativeObject);
     }
 
+    private void registerNativeAllocation$ravenwood() {
+        // No-op under ravenwood
+    }
+
     private LongArrayMultiStateCounter(Parcel in) {
         mNativeObject = native_initFromParcel(in);
-        sRegistry.registerNativeAllocation(this, mNativeObject);
+        registerNativeAllocation();
 
         mStateCount = native_getStateCount(mNativeObject);
         mLength = native_getArrayLength(mNativeObject);
@@ -361,10 +399,10 @@
             long longArrayContainerNativeObject, int state);
 
     @FastNative
-    private native String native_toString(long nativeObject);
+    private static native String native_toString(long nativeObject);
 
     @FastNative
-    private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+    private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
 
     @FastNative
     private static native long native_initFromParcel(Parcel parcel);
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 0645eb7..bbcea8a 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -36,6 +36,7 @@
  *
  * @hide Only for use within the system server.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LooperStats implements Looper.Observer {
     public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
     private static final int SESSION_POOL_SIZE = 50;
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index 661628a..c3bcfa6 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -40,6 +40,7 @@
  * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
  * on reboot, but keeps going.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MonotonicClock {
     private static final String TAG = "MonotonicClock";
 
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 1a7efac..56263fb 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -41,6 +41,7 @@
  * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
  * details.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PowerStats {
     private static final String TAG = "PowerStats";
 
@@ -67,6 +68,7 @@
      * This descriptor is used for storing PowerStats and can also be used by power models
      * to adjust the algorithm in accordance with the stats available on the device.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class Descriptor {
         public static final String XML_TAG_DESCRIPTOR = "descriptor";
         private static final String XML_ATTR_ID = "id";
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 9ddb8c7..6b85e08 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -34,6 +34,7 @@
  * 3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128
  * 4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ProcLocksReader {
     private final String mPath;
     private ProcFileReader mReader = null;
diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java
index 0002447f..b67190b 100644
--- a/core/java/com/android/internal/os/ProcStatsUtil.java
+++ b/core/java/com/android/internal/os/ProcStatsUtil.java
@@ -30,6 +30,7 @@
  * Utility functions for reading {@code proc} files
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class ProcStatsUtil {
 
     private static final boolean DEBUG = false;
@@ -92,10 +93,24 @@
      * seen, or at the end of the file
      */
     @Nullable
+    @android.ravenwood.annotation.RavenwoodReplace
     public static String readTerminatedProcFile(String path, byte terminator) {
         // Permit disk reads here, as /proc isn't really "on disk" and should be fast.
         // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps?
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            return readTerminatedProcFileInternal(path, terminator);
+        } finally {
+            StrictMode.setThreadPolicy(savedPolicy);
+        }
+    }
+
+    public static String readTerminatedProcFile$ravenwood(String path, byte terminator) {
+        // No StrictMode under Ravenwood
+        return readTerminatedProcFileInternal(path, terminator);
+    }
+
+    private static String readTerminatedProcFileInternal(String path, byte terminator) {
         try (FileInputStream is = new FileInputStream(path)) {
             ByteArrayOutputStream byteStream = null;
             final byte[] buffer = new byte[READ_SIZE];
@@ -147,8 +162,6 @@
                 Slog.d(TAG, "Failed to open proc file", e);
             }
             return null;
-        } finally {
-            StrictMode.setThreadPolicy(savedPolicy);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java
index 9b03469..2d485da 100644
--- a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java
+++ b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java
@@ -38,6 +38,7 @@
  * This provides the number of bytes/chars read/written in foreground/background for each uid.
  * The file contains a monotonically increasing count of bytes/chars for a single boot.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class StoragedUidIoStatsReader {
 
     private static final String TAG = StoragedUidIoStatsReader.class.getSimpleName();
@@ -73,8 +74,21 @@
      *
      * @param callback The callback to invoke for each line of the proc file.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public void readAbsolute(Callback callback) {
         final int oldMask = StrictMode.allowThreadDiskReadsMask();
+        try {
+            readAbsoluteInternal(callback);
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
+        }
+    }
+
+    public void readAbsolute$ravenwood(Callback callback) {
+        readAbsoluteInternal(callback);
+    }
+
+    private void readAbsoluteInternal(Callback callback) {
         File file = new File(sUidIoFile);
         try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
             String line;
@@ -106,8 +120,6 @@
             }
         } catch (IOException e) {
             Slog.e(TAG, "Failed to read " + sUidIoFile + ": " + e.getMessage());
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
         }
     }
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index dd310dc..201b23c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -118,6 +118,7 @@
 import android.window.ProxyOnBackInvokedDispatcher;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.IconMenuPresenter;
 import com.android.internal.view.menu.ListMenuPresenter;
@@ -374,7 +375,8 @@
 
     boolean mDecorFitsSystemWindows = true;
 
-    private final boolean mDefaultEdgeToEdge;
+    @VisibleForTesting
+    public final boolean mDefaultEdgeToEdge;
 
     private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
 
@@ -2448,6 +2450,7 @@
         // Apply data from current theme.
 
         TypedArray a = getWindowStyle();
+        WindowManager.LayoutParams params = getAttributes();
 
         if (false) {
             System.out.println("From style:");
@@ -2467,8 +2470,11 @@
             setFlags(0, flagsToUpdate);
         } else {
             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
-            getAttributes().setFitInsetsSides(0);
-            getAttributes().setFitInsetsTypes(0);
+            params.setFitInsetsSides(0);
+            params.setFitInsetsTypes(0);
+            if (mDefaultEdgeToEdge) {
+                params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            }
         }
 
         if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
@@ -2586,8 +2592,6 @@
                     R.styleable.Window_enforceNavigationBarContrast, true);
         }
 
-        WindowManager.LayoutParams params = getAttributes();
-
         // Non-floating windows on high end devices must put up decor beneath the system bars and
         // therefore must know about visibility changes of those.
         if (!mIsFloating) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f365dbb..2a744e3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,8 +49,6 @@
         "-Wno-unused-parameter",
         "-Wunused",
         "-Wunreachable-code",
-
-        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
@@ -284,8 +282,6 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
-                "libtextclassifier_hash_static",
-                "libexpresslog_jni",
             ],
 
             shared_libs: [
@@ -372,7 +368,6 @@
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
                 "flatbuffer_headers",
-                "libtextclassifier_hash_headers",
                 "tensorflow_headers",
             ],
             runtime_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 50253cf..c24d21d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -201,7 +201,6 @@
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1590,7 +1589,6 @@
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
-        REG_JNI(register_com_android_modules_expresslog_Utils),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index dab47e9..76b05ea 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -107,7 +107,7 @@
     *vector = counter->getCount(state);
 }
 
-static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
     return env->NewStringUTF(counter->toString().c_str());
@@ -127,7 +127,7 @@
         }                                     \
     }
 
-static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -161,7 +161,7 @@
         }                                    \
     }
 
-static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
+static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) {
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     int32_t stateCount;
@@ -253,7 +253,7 @@
     return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
 }
 
-static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRO scopedArray(env, jarray);
@@ -264,7 +264,7 @@
     std::copy(array, array + size, vector->data());
 }
 
-static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
@@ -273,7 +273,7 @@
     std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
 }
 
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                         jlongArray jarray, jintArray jindexMap) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 3887dd7..9ca1849 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -99,6 +99,7 @@
         optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_magnification_two_finger_triple_tap_enabled = 53 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto qs_targets = 54 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     }
     optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd93586..c6a241f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4806,6 +4806,13 @@
     <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
                 android:protectionLevel="signature" />
 
+    <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing")
+    @hide
+    @TestApi
+    Allows an accessibility service to observe motion events without consuming them. -->
+    <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to collect frame statistics -->
     <permission android:name="android.permission.FRAME_STATS"
          android:protectionLevel="signature" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 387a108..bf8e55f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6856,4 +6856,7 @@
     <!-- Whether the View-based scroll haptic feedback implementation is enabled for
          {@link InputDevice#SOURCE_ROTARY_ENCODER}s. -->
     <bool name="config_viewBasedRotaryEncoderHapticsEnabled">false</bool>
+
+    <!-- Whether the media player is shown on the quick settings -->
+    <bool name="config_quickSettingsShowMediaPlayer">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a5b1028..5791ddb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5298,4 +5298,6 @@
   <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" />
   <java-symbol type="bool" name="config_viewRotaryEncoderHapticScrollFedbackEnabled" />
   <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
+
+  <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
 </resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 88abaa1..1a3ec27 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -7,19 +7,35 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "FrameworksCoreTests-aidl",
+    srcs: [
+        "src/**/I*.aidl",
+        "aidl/**/I*.aidl",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "FrameworksCoreTests-helpers",
+    srcs: [
+        "DisabledTestApp/src/**/*.java",
+        "EnabledTestApp/src/**/*.java",
+        "BinderProxyCountingTestApp/src/**/*.java",
+        "BinderProxyCountingTestService/src/**/*.java",
+        "BinderDeathRecipientHelperApp/src/**/*.java",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 android_test {
     name: "FrameworksCoreTests",
 
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
-        "src/**/I*.aidl",
-        "DisabledTestApp/src/**/*.java",
-        "EnabledTestApp/src/**/*.java",
-        "BinderProxyCountingTestApp/src/**/*.java",
-        "BinderProxyCountingTestService/src/**/*.java",
-        "BinderDeathRecipientHelperApp/src/**/*.java",
-        "aidl/**/I*.aidl",
+        ":FrameworksCoreTests-aidl",
+        ":FrameworksCoreTests-helpers",
         ":FrameworksCoreTestDoubles-sources",
     ],
 
@@ -177,16 +193,29 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+        "flag-junit",
         "mockito_ravenwood",
         "platform-test-annotations",
         "flag-junit",
+        "testng",
     ],
     srcs: [
-        "src/android/os/BuildTest.java",
-        "src/android/os/FileUtilsTest.java",
+        "src/android/os/**/*.java",
+        "src/com/android/internal/os/**/*.java",
         "src/android/util/**/*.java",
+        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
-        "testdoubles/src/com/android/internal/util/**/*.java",
+        "src/com/android/internal/power/EnergyConsumerStatsTest.java",
+        ":FrameworksCoreTests{.aapt.srcjar}",
+        ":FrameworksCoreTests-aidl",
+        ":FrameworksCoreTests-helpers",
+        ":FrameworksCoreTestDoubles-sources",
     ],
+    aidl: {
+        generate_get_transaction_name: true,
+        local_include_dirs: ["aidl"],
+    },
     auto_gen_config: true,
 }
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 3768063..cd6abdd 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -20,9 +20,13 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 
 /**
@@ -35,7 +39,10 @@
  *  atest FrameworksCoreTests:PropertyInvalidatedCacheTests
  */
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = PropertyInvalidatedCache.class)
 public class PropertyInvalidatedCacheTests {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     // Configuration for creating caches
     private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
index d0c3470..006828f 100644
--- a/core/tests/coretests/src/android/os/AidlTest.java
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -16,23 +16,36 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
 
 import com.google.android.collect.Lists;
 
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
 
 import java.util.List;
 
-public class AidlTest extends TestCase {
+@IgnoreUnderRavenwood(blockedBy = Parcel.class)
+public class AidlTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private IAidlTest mRemote;
     private AidlObject mLocal;
     private NonAutoGeneratedObject mNonAutoGenerated;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mLocal = new AidlObject();
         mRemote = IAidlTest.Stub.asInterface(mLocal);
         mNonAutoGenerated = new NonAutoGeneratedObject("NonAutoGeneratedObject");
@@ -212,12 +225,14 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testInt() throws Exception {
         int result = mRemote.intMethod(42);
         assertEquals(42, result);
     }
 
+    @Test
     @SmallTest
     public void testParcelableIn() throws Exception {
         TestParcelable arg = new TestParcelable(43, "hi");
@@ -228,6 +243,7 @@
         assertEquals(44, result.mAnInt);
     }
 
+    @Test
     @SmallTest
     public void testParcelableOut() throws Exception {
         TestParcelable arg = new TestParcelable(43, "hi");
@@ -236,6 +252,7 @@
         assertEquals(44, arg.mAnInt);
     }
 
+    @Test
     @SmallTest
     public void testParcelableInOut() throws Exception {
         TestParcelable arg = new TestParcelable(43, "hi");
@@ -244,6 +261,7 @@
         assertEquals(44, arg.mAnInt);
     }
 
+    @Test
     @SmallTest
     public void testListParcelableLonger() throws Exception {
         List<TestParcelable> list = Lists.newArrayList();
@@ -268,6 +286,7 @@
         assertNotSame(list.get(1), list.get(2));
     }
 
+    @Test
     @SmallTest
     public void testListParcelableShorter() throws Exception {
         List<TestParcelable> list = Lists.newArrayList();
@@ -290,6 +309,7 @@
         assertNotSame(list.get(0), list.get(1));
     }
 
+    @Test
     @SmallTest
     public void testArrays() throws Exception {
         // boolean
@@ -363,14 +383,14 @@
         float[] fr = mRemote.floatArray(f0, f1, f2);
 
         assertEquals(1, fr.length);
-        assertEquals(90.1f, fr[0]);
+        assertEquals(90.1f, fr[0], 0.0f);
 
-        assertEquals(90.1f, f1[0]);
+        assertEquals(90.1f, f1[0], 0.0f);
         assertEquals(0f, f1[1], 0.0f);
 
-        assertEquals(90.1f, f2[0]);
-        assertEquals(90.5f, f2[1]);
-        assertEquals(90.6f, f2[2]);
+        assertEquals(90.1f, f2[0], 0.0f);
+        assertEquals(90.5f, f2[1], 0.0f);
+        assertEquals(90.6f, f2[2], 0.0f);
 
         // double
         double[] d0 = new double[]{100.1};
@@ -379,14 +399,14 @@
         double[] dr = mRemote.doubleArray(d0, d1, d2);
 
         assertEquals(1, dr.length);
-        assertEquals(100.1, dr[0]);
+        assertEquals(100.1, dr[0], 0.0);
 
-        assertEquals(100.1, d1[0]);
+        assertEquals(100.1, d1[0], 0.0);
         assertEquals(0, d1[1], 0.0);
 
-        assertEquals(100.1, d2[0]);
-        assertEquals(100.5, d2[1]);
-        assertEquals(100.6, d2[2]);
+        assertEquals(100.1, d2[0], 0.0);
+        assertEquals(100.5, d2[1], 0.0);
+        assertEquals(100.6, d2[2], 0.0);
 
         // String
         String[] s0 = new String[]{"s0[0]"};
@@ -405,6 +425,7 @@
         assertEquals("s2[2]", s2[2]);
     }
 
+    @Test
     @SmallTest
     public void testVoidSecurityException() throws Exception {
         boolean good = false;
@@ -416,6 +437,7 @@
         assertEquals(good, true);
     }
 
+    @Test
     @SmallTest
     public void testIntSecurityException() throws Exception {
         boolean good = false;
@@ -427,6 +449,7 @@
         assertEquals(good, true);
     }
 
+    @Test
     @SmallTest
     public void testGetTransactionNameAutoGenerated() throws Exception {
         assertEquals(15, mLocal.getMaxTransactionId());
@@ -446,6 +469,7 @@
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn));
     }
 
+    @Test
     @SmallTest
     public void testGetTransactionNameNonAutoGenerated() throws Exception {
         assertEquals(0, mNonAutoGenerated.getMaxTransactionId());
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
index 2cce43f..eff52f0 100644
--- a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -25,6 +25,8 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -36,6 +38,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,6 +55,7 @@
  * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks.
  */
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
 public class BinderDeathRecipientTest {
     private static final String TAG = BinderDeathRecipientTest.class.getSimpleName();
     private static final String TEST_PACKAGE_NAME_1 =
@@ -59,6 +63,9 @@
     private static final String TEST_PACKAGE_NAME_2 =
             "com.android.frameworks.coretests.bdr_helper_app2";
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Context mContext;
     private Handler mHandler;
     private ActivityManager mActivityManager;
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index 2089c6c..bcd9521 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -35,7 +37,8 @@
 import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
 import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
 
-import org.junit.BeforeClass;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,6 +74,7 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
 public class BinderProxyCountingTest {
     private static final String TAG = BinderProxyCountingTest.class.getSimpleName();
 
@@ -107,11 +111,14 @@
     };
     private static int sTestPkgUid;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     /**
      * Setup any common data for the upcoming tests.
      */
-    @BeforeClass
-    public static void setUpOnce() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         sContext = InstrumentationRegistry.getContext();
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0);
         ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid,
diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java
index 3567d17..a903ed9 100644
--- a/core/tests/coretests/src/android/os/BinderProxyTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyTest.java
@@ -16,19 +16,34 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class BinderProxyTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+public class BinderProxyTest {
     private static class CountingListener implements Binder.ProxyTransactListener {
         int mStartedCount;
         int mEndedCount;
@@ -43,17 +58,22 @@
         }
     };
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private Context mContext;
     private PowerManager mPowerManager;
 
     /**
      * Setup any common data for the upcoming tests.
      */
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
     }
 
+    @Test
     @MediumTest
     public void testNoListener() throws Exception {
         CountingListener listener = new CountingListener();
@@ -66,6 +86,7 @@
         assertEquals(0, listener.mEndedCount);
     }
 
+    @Test
     @MediumTest
     public void testListener() throws Exception {
         CountingListener listener = new CountingListener();
@@ -77,6 +98,7 @@
         assertEquals(1, listener.mEndedCount);
     }
 
+    @Test
     @MediumTest
     public void testSessionPropagated() throws Exception {
         Binder.setProxyTransactListener(new Binder.ProxyTransactListener() {
@@ -95,6 +117,7 @@
 
     private IBinder mRemoteBinder = null;
 
+    @Test
     @MediumTest
     public void testGetExtension() throws Exception {
         final CountDownLatch bindLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 02f8790..6c8b69f 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -16,21 +16,35 @@
 
 package android.os;
 
-import androidx.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.testng.Assert.assertThrows;
 
-public class BinderTest extends TestCase {
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@IgnoreUnderRavenwood(blockedBy = WorkSource.class)
+public class BinderTest {
     private static final int UID = 100;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
     @SmallTest
     public void testSetWorkSource() throws Exception {
         Binder.setCallingWorkSourceUid(UID);
         assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
 
+    @Test
     @SmallTest
     public void testClearWorkSource() throws Exception {
         Binder.setCallingWorkSourceUid(UID);
@@ -38,6 +52,7 @@
         assertEquals(-1, Binder.getCallingWorkSourceUid());
     }
 
+    @Test
     @SmallTest
     public void testRestoreWorkSource() throws Exception {
         Binder.setCallingWorkSourceUid(UID);
@@ -46,11 +61,13 @@
         assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
 
+    @Test
     @SmallTest
     public void testGetCallingUidOrThrow_throws() throws Exception {
         assertThrows(IllegalStateException.class, () -> Binder.getCallingUidOrThrow());
     }
 
+    @Test
     @SmallTest
     public void testGetExtension() throws Exception {
         Binder binder = new Binder();
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
index 48c9df6..4172bff 100644
--- a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
@@ -16,21 +16,42 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.IOException;
 
 /**
  * Test whether Binder calls inherit thread priorities correctly.
  */
-public class BinderThreadPriorityTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
+public class BinderThreadPriorityTest {
     private static final String TAG = "BinderThreadPriorityTest";
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private Context mContext;
     private IBinderThreadPriorityService mService;
     private int mSavedPriority;
 
@@ -55,12 +76,11 @@
         private static void fail() { throw new RuntimeException("unimplemented"); }
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        getContext().bindService(
-                new Intent(getContext(), BinderThreadPriorityService.class),
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mContext.bindService(
+                new Intent(mContext, BinderThreadPriorityService.class),
                 mConnection, Context.BIND_AUTO_CREATE);
 
         synchronized (this) {
@@ -80,8 +100,8 @@
         Log.i(TAG, "Saved priority: " + mSavedPriority);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         // HACK -- see bug 2665914 -- setThreadPriority() doesn't always set the
         // scheduler group reliably unless we start out with background priority.
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -89,8 +109,7 @@
         assertEquals(mSavedPriority, Process.getThreadPriority(Process.myTid()));
         assertEquals(expectedSchedulerGroup(mSavedPriority), getSchedulerGroup());
 
-        getContext().unbindService(mConnection);
-        super.tearDown();
+        mContext.unbindService(mConnection);
     }
 
     public static String getSchedulerGroup() {
@@ -111,6 +130,7 @@
         return "/";
     }
 
+    @Test
     public void testPassPriorityToService() throws Exception {
         for (int prio = 19; prio >= -20; prio--) {
             Process.setThreadPriority(prio);
@@ -125,6 +145,7 @@
         }
     }
 
+    @Test
     public void testCallBackFromServiceWithPriority() throws Exception {
         for (int prio = -20; prio <= 19; prio++) {
             final int expected = prio;
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index b14c88f..552066c 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -19,11 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -33,6 +36,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -42,12 +46,16 @@
 @LargeTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
 public class BinderWorkSourceTest {
     private static Context sContext;
     private static final int UID = 100;
     private static final int SECOND_UID = 200;
     private static final int UID_NONE = ThreadLocalWorkSource.UID_NONE;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private IBinderWorkSourceService mService;
     private IBinderWorkSourceNestedService mNestedService;
 
@@ -71,13 +79,9 @@
         }
     };
 
-    @BeforeClass
-    public static void setUpOnce() throws Exception {
-        sContext = InstrumentationRegistry.getContext();
-    }
-
     @Before
     public void setUp() throws Exception {
+        sContext = InstrumentationRegistry.getContext();
         sContext.bindService(
                 new Intent(sContext, BinderWorkSourceService.class),
                 mConnection, Context.BIND_AUTO_CREATE);
diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java
index b4c47af9..7829457 100644
--- a/core/tests/coretests/src/android/os/BroadcasterTest.java
+++ b/core/tests/coretests/src/android/os/BroadcasterTest.java
@@ -16,16 +16,26 @@
 
 package android.os;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class BroadcasterTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class BroadcasterTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final int MESSAGE_A = 23234;
     private static final int MESSAGE_B = 3;
     private static final int MESSAGE_C = 14;
     private static final int MESSAGE_D = 95;
 
+    @Test
     @MediumTest
     public void test1() throws Exception {
         /*
@@ -103,6 +113,7 @@
         }
     }
 
+    @Test
     @MediumTest
     public void test2() throws Exception {
         /*
@@ -112,6 +123,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void test3() throws Exception {
         /*
@@ -121,6 +133,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void test4() throws Exception {
         /*
@@ -156,6 +169,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void test5() throws Exception {
         /*
@@ -191,6 +205,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void test6() throws Exception {
         /*
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index a3bda8b..e7b5dff6 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -23,13 +23,16 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -45,6 +48,9 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BundleTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Log.TerribleFailureHandler mWtfHandler;
 
     @After
@@ -115,6 +121,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
     public void testCreateFromParcel() throws Exception {
         boolean withFd;
         Parcel p;
@@ -295,6 +302,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void kindofEquals_lazyValuesAndDifferentClassLoaders_returnsFalse() {
         Parcelable p1 = new CustomParcelable(13, "Tiramisu");
         Parcelable p2 = new CustomParcelable(13, "Tiramisu");
@@ -350,6 +358,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void readWriteLengthMismatch_logsWtf() throws Exception {
         mWtfHandler = Log.setWtfHandler((tag, e, system) -> {
             throw new RuntimeException(e);
@@ -364,6 +373,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception {
         Bundle.setShouldDefuse(false);
         Bundle bundle = new Bundle();
@@ -376,6 +386,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception {
         Bundle.setShouldDefuse(true);
         Bundle bundle = new Bundle();
@@ -391,6 +402,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception {
         Bundle.setShouldDefuse(true);
         Bundle bundle = new Bundle();
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
index 42c97f3..c2cea0a 100644
--- a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -22,6 +22,8 @@
 
 import android.content.Context;
 import android.os.CancellationSignalBeamer.Receiver;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.PollingCheck;
 import android.util.PollingCheck.PollingCheckCondition;
 
@@ -29,6 +31,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,11 +44,20 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = CancellationSignalBeamer.class)
 public class CancellationSignalBeamerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
-    private CancellationSignal mSenderSignal = new CancellationSignal();
+    private CancellationSignal mSenderSignal;
     private CancellationSignal mReceivedSignal;
-    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mSenderSignal = new CancellationSignal();
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
 
     @Test
     public void testBeam_null() {
@@ -208,17 +221,22 @@
         mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
     }
 
-    private final Sender mSender = new Sender() {
-        @Override
-        public void onCancel(IBinder token) {
-            mReceiver.cancel(token);
-        }
+    private Sender mSender;
+    private Receiver mReceiver;
 
-        @Override
-        public void onForget(IBinder token) {
-            mReceiver.forget(token);
-        }
-    };
+    @Before
+    public void setUpSenderReceiver() {
+        mSender = new Sender() {
+            @Override
+            public void onCancel(IBinder token) {
+                mReceiver.cancel(token);
+            }
 
-    private final Receiver mReceiver = new Receiver(false);
+            @Override
+            public void onForget(IBinder token) {
+                mReceiver.forget(token);
+            }
+        };
+        mReceiver = new Receiver(false);
+    }
 }
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index ef38cde..1aa263f 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -28,12 +28,15 @@
 
 import android.content.Context;
 import android.os.storage.StorageManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,7 +46,11 @@
 import java.util.function.BiFunction;
 
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = Environment.class)
 public class EnvironmentTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File dir;
 
     private static Context getContext() {
diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java
index 708bfa6..726670b 100644
--- a/core/tests/coretests/src/android/os/FileBridgeTest.java
+++ b/core/tests/coretests/src/android/os/FileBridgeTest.java
@@ -19,12 +19,25 @@
 import static android.os.ParcelFileDescriptor.MODE_CREATE;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.os.FileBridge.FileBridgeOutputStream;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.test.MoreAsserts;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
 import libcore.io.Streams;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -32,18 +45,20 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Random;
 
-public class FileBridgeTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
+public class FileBridgeTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private File file;
     private ParcelFileDescriptor outputFile;
     private FileBridge bridge;
     private FileBridgeOutputStream client;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        file = getContext().getFileStreamPath("meow.dat");
+    @Before
+    public void setUp() throws Exception {
+        file = File.createTempFile("meow", "dat");
         file.delete();
 
         outputFile = ParcelFileDescriptor.open(file, MODE_CREATE | MODE_READ_WRITE);
@@ -54,8 +69,8 @@
         client = new FileBridgeOutputStream(bridge.getClientSocket());
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         outputFile.close();
         file.delete();
     }
@@ -76,17 +91,20 @@
         MoreAsserts.assertEquals(expected, Streams.readFully(new FileInputStream(file)));
     }
 
+    @Test
     public void testNoWriteNoSync() throws Exception {
         assertOpen();
         closeAndAssertClosed();
     }
 
+    @Test
     public void testNoWriteSync() throws Exception {
         assertOpen();
         client.flush();
         closeAndAssertClosed();
     }
 
+    @Test
     public void testWriteNoSync() throws Exception {
         assertOpen();
         client.write("meow".getBytes(StandardCharsets.UTF_8));
@@ -94,6 +112,7 @@
         assertContents("meow".getBytes(StandardCharsets.UTF_8));
     }
 
+    @Test
     public void testWriteSync() throws Exception {
         assertOpen();
         client.write("cake".getBytes(StandardCharsets.UTF_8));
@@ -102,6 +121,7 @@
         assertContents("cake".getBytes(StandardCharsets.UTF_8));
     }
 
+    @Test
     public void testWriteSyncWrite() throws Exception {
         assertOpen();
         client.write("meow".getBytes(StandardCharsets.UTF_8));
@@ -111,6 +131,7 @@
         assertContents("meowcake".getBytes(StandardCharsets.UTF_8));
     }
 
+    @Test
     public void testEmptyWrite() throws Exception {
         assertOpen();
         client.write(new byte[0]);
@@ -118,6 +139,7 @@
         assertContents(new byte[0]);
     }
 
+    @Test
     public void testWriteAfterClose() throws Exception {
         assertOpen();
         client.write("meow".getBytes(StandardCharsets.UTF_8));
@@ -130,6 +152,7 @@
         assertContents("meow".getBytes(StandardCharsets.UTF_8));
     }
 
+    @Test
     public void testRandomWrite() throws Exception {
         final Random r = new Random();
         final ByteArrayOutputStream result = new ByteArrayOutputStream();
@@ -146,6 +169,7 @@
         assertContents(result.toByteArray());
     }
 
+    @Test
     public void testGiantWrite() throws Exception {
         final byte[] test = new byte[263401];
         new Random().nextBytes(test);
diff --git a/core/tests/coretests/src/android/os/FileObserverTest.java b/core/tests/coretests/src/android/os/FileObserverTest.java
index ece7645..3cd8045 100644
--- a/core/tests/coretests/src/android/os/FileObserverTest.java
+++ b/core/tests/coretests/src/android/os/FileObserverTest.java
@@ -16,21 +16,37 @@
 
 package android.os;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
-public class FileObserverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = FileObserver.class)
+public class FileObserverTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Observer mObserver;
     private File mTestFile;
 
@@ -57,18 +73,19 @@
         }
     }
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         mTestFile = File.createTempFile(".file_observer_test", ".txt");
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mTestFile != null && mTestFile.exists()) {
             mTestFile.delete();
         }
     }
 
+    @Test
     @MediumTest
     public void testRun() throws Exception {
         // make file changes and wait for them
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 93cfc40..0bac1c7 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -16,18 +16,35 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class HandlerThreadTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class HandlerThreadTest {
     private static final int TEST_WHAT = 1;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private boolean mGotMessage = false;
     private int mGotMessageWhat = -1;
     private volatile boolean mDidSetup = false;
     private volatile int mLooperTid = -1;
-    
+
+    @Test
     @MediumTest
     public void testHandlerThread() throws Exception {
         HandlerThread th1 =  new HandlerThread("HandlerThreadTest") {
diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java
index d8886c9..8644663 100644
--- a/core/tests/coretests/src/android/os/IdleHandlerTest.java
+++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java
@@ -17,12 +17,19 @@
 package android.os;
 
 import android.os.MessageQueue.IdleHandler;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class IdleHandlerTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class IdleHandlerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private static class BaseTestHandler extends TestHandlerThread {
         Handler mHandler;
@@ -54,6 +61,7 @@
         }
     }
 
+    @Test
     @MediumTest
     public void testOneShotFirst() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
@@ -88,6 +96,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void testOneShotLater() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
@@ -125,6 +134,7 @@
     }
 
 
+    @Test
     @MediumTest
     public void testRepeatedFirst() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
@@ -159,6 +169,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void testRepeatedLater() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index 34712ce..b03fd64 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -17,12 +17,14 @@
 package android.os;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 
 /**
@@ -35,7 +37,10 @@
  *  atest FrameworksCoreTests:IpcDataCacheTest
  */
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = IpcDataCache.class)
 public class IpcDataCacheTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     // Configuration for creating caches
     private static final String MODULE = IpcDataCache.MODULE_TEST;
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 88fc826..0025e3a 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -16,13 +16,29 @@
 
 package android.os;
 
-import androidx.test.filters.SmallTest;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
 
-import junit.framework.TestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Locale;
 
-public class LocaleListTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = LocaleList.class)
+public class LocaleListTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
     @SmallTest
     public void testConstructor() throws Exception {
         LocaleList ll;
@@ -51,6 +67,7 @@
         assertEquals("fr,de", ll.toLanguageTags());
     }
 
+    @Test
     @SmallTest
     public void testConstructor_nullThrows() throws Exception {
         try {
@@ -61,6 +78,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testGetDefault_localeSetDefaultCalledButNoChangeNecessary() throws Exception {
         final Locale originalLocale = Locale.getDefault();
@@ -82,6 +100,7 @@
         LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
     }
 
+    @Test
     @SmallTest
     public void testIntersection() {
         LocaleList localesWithN = new LocaleList(
diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java
index 05c2995..a695424 100644
--- a/core/tests/coretests/src/android/os/MemoryFileTest.java
+++ b/core/tests/coretests/src/android/os/MemoryFileTest.java
@@ -16,11 +16,21 @@
 
 package android.os;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -29,7 +39,11 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class MemoryFileTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = MemoryFile.class)
+public class MemoryFileTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private void compareBuffers(byte[] buffer1, byte[] buffer2, int length) throws Exception {
         for (int i = 0; i < length; i++) {
@@ -44,6 +58,8 @@
      */
     // Flaky test - temporarily suppress from large suite for now
     // @LargeTest
+    @Test
+    @Ignore("Flaky test")
     public void testPurge() throws Exception {
         List<MemoryFile> files = new ArrayList<MemoryFile>();
         try {
@@ -70,6 +86,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testRun() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
@@ -102,6 +119,7 @@
     }
 
     // http://code.google.com/p/android/issues/detail?id=11415
+    @Test
     public void testOutputStreamAdvances() throws IOException {
         MemoryFile file = new MemoryFile("MemoryFileTest", 10);
 
@@ -142,24 +160,28 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testReadNegativeOffset() throws Exception {
         readIndexOutOfBoundsException(-1, 5,
                 "read() with negative offset should throw IndexOutOfBoundsException");
     }
 
+    @Test
     @SmallTest
     public void testReadNegativeCount() throws Exception {
         readIndexOutOfBoundsException(5, -1,
                 "read() with negative length should throw IndexOutOfBoundsException");
     }
 
+    @Test
     @SmallTest
     public void testReadOffsetOverflow() throws Exception {
         readIndexOutOfBoundsException(testString.length + 10, 5,
                 "read() with offset outside buffer should throw IndexOutOfBoundsException");
     }
 
+    @Test
     @SmallTest
     public void testReadOffsetCountOverflow() throws Exception {
         readIndexOutOfBoundsException(testString.length, 11,
@@ -167,6 +189,7 @@
     }
 
     // Test behavior of read() at end of file
+    @Test
     @SmallTest
     public void testReadEOF() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", testString.length);
@@ -189,6 +212,7 @@
     }
 
     // Tests that close() is idempotent
+    @Test
     @SmallTest
     public void testCloseClose() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
@@ -199,6 +223,7 @@
     }
 
     // Tests that we can't read from a closed memory file
+    @Test
     @SmallTest
     public void testCloseRead() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
@@ -214,6 +239,7 @@
     }
 
     // Tests that we can't write to a closed memory file
+    @Test
     @SmallTest
     public void testCloseWrite() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
@@ -229,6 +255,7 @@
     }
 
     // Tests that we can't call allowPurging() after close()
+    @Test
     @SmallTest
     public void testCloseAllowPurging() throws Exception {
         MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
@@ -245,6 +272,7 @@
     }
 
     // Tests that we don't leak file descriptors or mmap areas
+    @Test
     @LargeTest
     public void testCloseLeak() throws Exception {
         // open enough memory files that we should run out of
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 2c5588e..851e612 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -16,13 +16,22 @@
 
 package android.os;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.Suppress;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @Suppress  // Failing.
-public class MessageQueueTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class MessageQueueTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private static class BaseTestHandler extends TestHandlerThread {
         Handler mHandler;
@@ -61,6 +70,7 @@
         }
     }
 
+    @Test
     @MediumTest
     public void testMessageOrder() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
@@ -80,6 +90,7 @@
         tester.doTest(1000);
     }
 
+    @Test
     @MediumTest
     public void testAtFrontOfQueue() throws Exception {
         TestHandlerThread tester = new BaseTestHandler() {
@@ -141,7 +152,9 @@
         }
     }
 
+    @Test
     @MediumTest
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testFieldIntegrity() throws Exception {
 
         TestHandlerThread tester = new TestFieldIntegrityHandler() {
@@ -157,7 +170,7 @@
             public void handleMessage(Message msg) {
                 super.handleMessage(msg);
                 if (msg.what == 0) {
-                    msg.flags = -1;
+                    msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM;
                     msg.what = 1;
                     msg.arg1 = 456;
                     msg.arg2 = 789;
diff --git a/core/tests/coretests/src/android/os/MessengerTest.java b/core/tests/coretests/src/android/os/MessengerTest.java
index 9143ff1..eb6263f 100644
--- a/core/tests/coretests/src/android/os/MessengerTest.java
+++ b/core/tests/coretests/src/android/os/MessengerTest.java
@@ -16,15 +16,31 @@
 
 package android.os;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
 
-public class MessengerTest extends AndroidTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
+public class MessengerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private Context mContext;
     private Messenger mServiceMessenger;
     
     private ServiceConnection mConnection = new ServiceConnection() {
@@ -86,10 +102,10 @@
         }
     };
     
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getContext().bindService(new Intent(mContext, MessengerService.class),
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mContext.bindService(new Intent(mContext, MessengerService.class),
                 mConnection, Context.BIND_AUTO_CREATE);
         synchronized (this) {
             while (mServiceMessenger == null) {
@@ -101,15 +117,14 @@
         }
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        getContext().unbindService(mConnection);
+    @After
+    public void tearDown() throws Exception {
+        mContext.unbindService(mConnection);
     }
 
+    @Test
     @MediumTest
     public void testSend() {
         (new TestThread()).doTest(1000);
-        
     }
 }
diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java
deleted file mode 100644
index 08fb945..0000000
--- a/core/tests/coretests/src/android/os/OsTests.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import junit.framework.TestSuite;
-
-public class OsTests {
-    public static TestSuite suite() {
-        TestSuite suite = new TestSuite(OsTests.class.getName());
-
-        suite.addTestSuite(AidlTest.class);
-        suite.addTestSuite(BroadcasterTest.class);
-        suite.addTestSuite(FileObserverTest.class);
-        suite.addTestSuite(IdleHandlerTest.class);
-        suite.addTestSuite(MessageQueueTest.class);
-        suite.addTestSuite(MessengerTest.class);
-        suite.addTestSuite(PatternMatcherTest.class);
-
-        return suite;
-    }
-}
diff --git a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
index b4e180c..ffeab29 100644
--- a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
+++ b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
@@ -20,11 +20,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArrayMap;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -36,6 +39,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class ParcelNullabilityTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void nullByteArray() {
         Parcel p = Parcel.obtain();
@@ -61,6 +67,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void nullCharSequence() {
         Parcel p = Parcel.obtain();
         p.writeCharSequence(null);
@@ -69,6 +76,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void nullStrongBinder() {
         Parcel p = Parcel.obtain();
         p.writeStrongBinder(null);
@@ -77,6 +85,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void nullStringInterface() {
         Parcel p = Parcel.obtain();
         p.writeStrongInterface(null);
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 4b993fa..26f6d69 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -21,22 +21,29 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ParcelTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final int WORK_SOURCE_1 = 1000;
     private static final int WORK_SOURCE_2 = 1002;
     private static final String INTERFACE_TOKEN_1 = "IBinder interface token";
     private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token";
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testIsForRpc() {
         Parcel p = Parcel.obtain();
         assertEquals(false, p.isForRpc());
@@ -44,6 +51,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCallingWorkSourceUidAfterWrite() {
         Parcel p = Parcel.obtain();
         // Method does not throw if replaceCallingWorkSourceUid is called before requests headers
@@ -64,6 +72,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCallingWorkSourceUidAfterEnforce() {
         Parcel p = Parcel.obtain();
         p.writeInterfaceToken(INTERFACE_TOKEN_1);
@@ -81,6 +90,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testParcelWithMultipleHeaders() {
         Parcel p = Parcel.obtain();
         Binder.setCallingWorkSourceUid(WORK_SOURCE_1);
@@ -138,6 +148,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenSameDataWithBinder() {
         Binder binder = new Binder();
         Parcel pA = Parcel.obtain();
@@ -297,6 +308,7 @@
      * and 1M length for complex objects are allowed.
      */
     @Test
+    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testAllocations_whenWithinLimit() {
         Binder.setIsDirectlyHandlingTransactionOverride(true);
         Parcel p = Parcel.obtain();
diff --git a/core/tests/coretests/src/android/os/PatternMatcherTest.java b/core/tests/coretests/src/android/os/PatternMatcherTest.java
index 82350cd..a5e036d 100644
--- a/core/tests/coretests/src/android/os/PatternMatcherTest.java
+++ b/core/tests/coretests/src/android/os/PatternMatcherTest.java
@@ -16,9 +16,10 @@
 
 package android.os;
 
-import androidx.test.filters.SmallTest;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import junit.framework.TestCase;
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,7 +27,7 @@
 
 @RunWith(JUnit4.class)
 @SmallTest
-public class PatternMatcherTest extends TestCase{
+public class PatternMatcherTest {
 
     @Test
     public void testAdvancedPatternMatchesAnyToken() {
diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
index 38ad90f..46f1706 100644
--- a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
@@ -16,33 +16,48 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import android.os.PerformanceCollector.PerformanceResultsWriter;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Random;
 
-public class PerformanceCollectorTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = PerformanceCollector.class)
+public class PerformanceCollectorTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private PerformanceCollector mPerfCollector;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mPerfCollector = new PerformanceCollector();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         mPerfCollector = null;
     }
 
+    @Test
     @SmallTest
     public void testBeginSnapshotNoWriter() throws Exception {
         mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter");
@@ -54,6 +69,7 @@
         assertEquals(2, snapshot.size());
     }
 
+    @Test
     @MediumTest
     public void testEndSnapshotNoWriter() throws Exception {
         mPerfCollector.beginSnapshot("testEndSnapshotNoWriter");
@@ -63,6 +79,7 @@
         verifySnapshotBundle(snapshot);
     }
 
+    @Test
     @SmallTest
     public void testStartTimingNoWriter() throws Exception {
         mPerfCollector.startTiming("testStartTimingNoWriter");
@@ -74,6 +91,7 @@
         verifyTimingBundle(measurement, new ArrayList<String>());
     }
 
+    @Test
     @SmallTest
     public void testAddIterationNoWriter() throws Exception {
         mPerfCollector.startTiming("testAddIterationNoWriter");
@@ -83,6 +101,7 @@
         verifyIterationBundle(iteration, "timing1");
     }
 
+    @Test
     @SmallTest
     public void testStopTimingNoWriter() throws Exception {
         mPerfCollector.startTiming("testStopTimingNoWriter");
@@ -100,6 +119,7 @@
         verifyTimingBundle(timing, labels);
     }
 
+    @Test
     @SmallTest
     public void testBeginSnapshot() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -114,6 +134,7 @@
         assertEquals(2, snapshot.size());
     }
 
+    @Test
     @MediumTest
     public void testEndSnapshot() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -127,6 +148,7 @@
         verifySnapshotBundle(snapshot1);
     }
 
+    @Test
     @SmallTest
     public void testStartTiming() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -141,6 +163,7 @@
         verifyTimingBundle(measurement, new ArrayList<String>());
     }
 
+    @Test
     @SmallTest
     public void testAddIteration() throws Exception {
         mPerfCollector.startTiming("testAddIteration");
@@ -150,6 +173,7 @@
         verifyIterationBundle(iteration, "timing5");
     }
 
+    @Test
     @SmallTest
     public void testStopTiming() throws Exception {
         mPerfCollector.startTiming("testStopTiming");
@@ -167,6 +191,7 @@
         verifyTimingBundle(timing, labels);
     }
 
+    @Test
     @SmallTest
     public void testAddMeasurementLong() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -188,6 +213,7 @@
         assertEquals(-19354, results.getLong("testAddMeasurementLongNeg"));
     }
 
+    @Test
     @SmallTest
     public void testAddMeasurementFloat() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -202,13 +228,14 @@
         Bundle results = writer.timingResults;
         assertEquals(4, results.size());
         assertTrue(results.containsKey("testAddMeasurementFloatZero"));
-        assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero"));
+        assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero"), 0.0f);
         assertTrue(results.containsKey("testAddMeasurementFloatPos"));
-        assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos"));
+        assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos"), 0.0f);
         assertTrue(results.containsKey("testAddMeasurementFloatNeg"));
-        assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg"));
+        assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg"), 0.0f);
     }
 
+    @Test
     @SmallTest
     public void testAddMeasurementString() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -230,6 +257,7 @@
         assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty"));
     }
 
+    @Test
     @MediumTest
     public void testSimpleSequence() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -262,6 +290,7 @@
         verifyTimingBundle(timing, labels);
     }
 
+    @Test
     @MediumTest
     public void testLongSequence() throws Exception {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
@@ -348,6 +377,7 @@
      * Verify that snapshotting and timing do not interfere w/ each other,
      * by staggering calls to snapshot and timing functions.
      */
+    @Test
     @MediumTest
     public void testOutOfOrderSequence() {
         MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 9b4dec4..12a2844 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -24,18 +24,25 @@
 import static org.junit.Assume.assumeNotNull;
 
 import android.os.PerformanceHintManager.Session;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = PerformanceHintManager.class)
 public class PerformanceHintManagerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final long RATE_1000 = 1000L;
     private static final long TARGET_166 = 166L;
     private static final long DEFAULT_TARGET_NS = 16666666L;
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 5d213ca..cb281ff 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -17,7 +17,9 @@
 package android.os;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -26,28 +28,34 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.os.Flags;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.AndroidTestCase;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiDevice;
 
 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.MockitoAnnotations;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-public class PowerManagerTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+public class PowerManagerTest {
 
     private static final String TAG = "PowerManagerTest";
+    private Context mContext;
     private PowerManager mPm;
     private UiDevice mUiDevice;
     private Executor mExec = Executors.newSingleThreadExecutor();
@@ -68,21 +76,27 @@
             String[] keys, String[] values);
 
     static {
-        System.loadLibrary("powermanagertest_jni");
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            System.loadLibrary("powermanagertest_jni");
+        }
     }
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null
+            : DeviceFlagsValueProvider.createCheckFlagsRule();
 
     /**
      * Setup any common data for the upcoming tests.
      */
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mUiDevice.executeShellCommand("cmd thermalservice override-status 0");
     }
@@ -100,6 +114,7 @@
      *
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testPreconditions() throws Exception {
         assertNotNull(mPm);
@@ -110,6 +125,7 @@
      *
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testNewWakeLock() throws Exception {
         PowerManager.WakeLock wl = mPm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "FULL_WAKE_LOCK");
@@ -133,6 +149,7 @@
      *
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testBadNewWakeLock() throws Exception {
         final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK
@@ -152,6 +169,7 @@
      *
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testWakeLockWithWorkChains() throws Exception {
         PowerManager.WakeLock wakeLock = mPm.newWakeLock(
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index b2ffdc0..310baa3 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -17,12 +17,23 @@
 
 package android.os;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-public class ProcessTest extends TestCase {
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@IgnoreUnderRavenwood(blockedBy = Process.class)
+public class ProcessTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private static final int BAD_PID = 0;
 
+    @Test
     public void testProcessGetUidFromName() throws Exception {
         assertEquals(android.os.Process.SYSTEM_UID, Process.getUidForName("system"));
         assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth"));
@@ -36,6 +47,7 @@
                 Process.getUidForName("u3_a100"));
     }
 
+    @Test
     public void testProcessGetUidFromNameFailure() throws Exception {
         // Failure cases
         assertEquals(-1, Process.getUidForName("u2a_foo"));
@@ -51,6 +63,7 @@
      * Tests getUidForPid() by ensuring that it returns the correct value when the process queried
      * doesn't exist.
      */
+    @Test
     public void testGetUidForPidInvalidPid() {
         assertEquals(-1, Process.getUidForPid(BAD_PID));
     }
@@ -59,6 +72,7 @@
      * Tests getParentPid() by ensuring that it returns the correct value when the process queried
      * doesn't exist.
      */
+    @Test
     public void testGetParentPidInvalidPid() {
         assertEquals(-1, Process.getParentPid(BAD_PID));
     }
@@ -67,11 +81,13 @@
      * Tests getThreadGroupLeader() by ensuring that it returns the correct value when the
      * thread queried doesn't exist.
      */
+    @Test
     public void testGetThreadGroupLeaderInvalidTid() {
         // This function takes a TID instead of a PID but BAD_PID should also be a bad TID.
         assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
     }
 
+    @Test
     public void testGetAdvertisedMem() {
         assertTrue(Process.getAdvertisedMem() > 0);
         assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
diff --git a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
index eff4826..25ce240 100644
--- a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
+++ b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
@@ -24,6 +24,8 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.system.Os;
 
 import androidx.test.InstrumentationRegistry;
@@ -31,6 +33,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,7 +44,11 @@
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = RedactingFileDescriptor.class)
 public class RedactingFileDescriptorTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Context mContext;
     private File mFile;
 
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index d07187c..593833ec 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -16,24 +16,36 @@
 
 package android.os;
 
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * This class is used to test the native tracing support.  Run this test
  * while tracing on the emulator and then run traceview to view the trace.
  */
-public class TraceTest extends AndroidTestCase
-{
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = Trace.class)
+public class TraceTest {
     private static final String TAG = "TraceTest";
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private int eMethodCalls = 0;
     private int fMethodCalls = 0;
     private int gMethodCalls = 0;
 
+    @Test
     public void testNullStrings() {
         Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
@@ -48,6 +60,7 @@
         Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, null);
     }
 
+    @Test
     @SmallTest
     public void testNativeTracingFromJava()
     {
@@ -80,7 +93,8 @@
 
     native void nativeMethod();
     native void nativeMethodAndStartTracing();
-    
+
+    @Test
     @LargeTest
     @Suppress  // Failing.
     public void testMethodTracing()
diff --git a/core/tests/coretests/src/android/os/VintfObjectTest.java b/core/tests/coretests/src/android/os/VintfObjectTest.java
index ae6e79a..f34b8fd 100644
--- a/core/tests/coretests/src/android/os/VintfObjectTest.java
+++ b/core/tests/coretests/src/android/os/VintfObjectTest.java
@@ -16,12 +16,27 @@
 
 package android.os;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertTrue;
 
-public class VintfObjectTest extends TestCase {
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = VintfObject.class)
+public class VintfObjectTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     /**
      * Quick check for {@link VintfObject#report VintfObject.report()}.
      */
+    @Test
     public void testReport() {
         String[] xmls = VintfObject.report();
         assertTrue(xmls.length > 0);
diff --git a/core/tests/coretests/src/android/os/WorkSourceParcelTest.java b/core/tests/coretests/src/android/os/WorkSourceParcelTest.java
index 4831606..5f49186 100644
--- a/core/tests/coretests/src/android/os/WorkSourceParcelTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceParcelTest.java
@@ -16,17 +16,25 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import static org.junit.Assert.assertEquals;
-
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(reason = "JNI")
 public class WorkSourceParcelTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     /**
      * END_OF_PARCEL_MARKER is added at the end of Parcel on native or java side on write and
      * then read on java or native side on read. This way we can ensure that no extra data
@@ -41,8 +49,11 @@
             String[] names, int parcelEndMarker);
 
     static {
-        System.loadLibrary("worksourceparceltest_jni");
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            System.loadLibrary("worksourceparceltest_jni");
+        }
     }
+
     /**
      * Confirm that we can pass WorkSource from native to Java.
      */
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 4206fd2..85dc127 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -16,9 +16,19 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
 import android.os.WorkSource.WorkChain;
 
-import junit.framework.TestCase;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -28,7 +38,9 @@
  *
  * These tests will be moved to CTS when finalized.
  */
-public class WorkSourceTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class WorkSourceTest {
+    @Test
     public void testWorkChain_add() {
         WorkChain wc1 = new WorkChain();
         wc1.addNode(56, null);
@@ -46,6 +58,7 @@
         assertEquals(2, wc1.getSize());
     }
 
+    @Test
     public void testWorkChain_equalsHashCode() {
         WorkChain wc1 = new WorkChain();
         WorkChain wc2 = new WorkChain();
@@ -78,6 +91,7 @@
         assertFalse(wc1.hashCode() == wc2.hashCode());
     }
 
+    @Test
     public void testWorkChain_constructor() {
         WorkChain wc1 = new WorkChain();
         wc1.addNode(1, "foo")
@@ -91,6 +105,7 @@
         assertFalse(wc1.equals(wc2));
     }
 
+    @Test
     public void testDiff_workChains() {
         WorkSource ws1 = new WorkSource();
         ws1.add(50);
@@ -104,6 +119,7 @@
         assertFalse(ws2.diff(ws1));
     }
 
+    @Test
     public void testEquals_workChains() {
         WorkSource ws1 = new WorkSource();
         ws1.add(50);
@@ -128,6 +144,7 @@
         assertFalse(ws3.equals(ws1));
     }
 
+    @Test
     public void testEquals_workChains_nullEmptyAreEquivalent() {
         // Construct a WorkSource that has no WorkChains, but whose workChains list
         // is non-null.
@@ -145,6 +162,7 @@
         assertFalse(ws1.equals(ws2));
     }
 
+    @Test
     public void testWorkSourceParcelling() {
         WorkSource ws = new WorkSource();
 
@@ -164,6 +182,7 @@
         assertEquals(unparcelled, ws);
     }
 
+    @Test
     public void testSet_workChains() {
         WorkSource ws1 = new WorkSource();
         ws1.add(50);
@@ -193,6 +212,7 @@
         assertEquals(1, ws1.getWorkChains().get(0).getSize());
     }
 
+    @Test
     public void testSet_nullWorkChain() {
         WorkSource ws = new WorkSource();
         ws.add(60);
@@ -203,6 +223,7 @@
         assertEquals(0, ws.getWorkChains().size());
     }
 
+    @Test
     public void testAdd_workChains() {
         WorkSource ws = new WorkSource();
         ws.createWorkChain().addNode(70, "foo");
@@ -224,6 +245,7 @@
         assertEquals(2, workChains.size());
     }
 
+    @Test
     public void testSet_noWorkChains() {
         WorkSource ws = new WorkSource();
         ws.set(10);
@@ -237,6 +259,7 @@
         assertEquals("foo", ws2.getPackageName(0));
     }
 
+    @Test
     public void testDiffChains_noChanges() {
         // WorkSources with no chains.
         assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource()));
@@ -254,6 +277,7 @@
         assertEquals(null, WorkSource.diffChains(ws2, ws1));
     }
 
+    @Test
     public void testDiffChains_noChains() {
         // Diffs against a worksource with no chains.
         WorkSource ws1 = new WorkSource();
@@ -276,6 +300,7 @@
         assertEquals(ws2.getWorkChains(), diffs[1]);
     }
 
+    @Test
     public void testDiffChains_onlyAdditionsOrRemovals() {
         WorkSource ws1 = new WorkSource();
         WorkSource ws2 = new WorkSource();
@@ -302,6 +327,7 @@
     }
 
 
+    @Test
     public void testDiffChains_generalCase() {
         WorkSource ws1 = new WorkSource();
         WorkSource ws2 = new WorkSource();
@@ -340,6 +366,7 @@
         assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1));
     }
 
+    @Test
     public void testGetAttributionId() {
         WorkSource ws = new WorkSource();
         WorkChain wc1 = ws.createWorkChain();
@@ -355,6 +382,7 @@
         assertEquals(100, ws.getAttributionUid());
     }
 
+    @Test
     public void testGetAttributionIdWithoutWorkChain() {
         WorkSource ws1 = new WorkSource(100);
         ws1.add(200);
@@ -365,6 +393,7 @@
         assertEquals(100, ws2.getAttributionUid());
     }
 
+    @Test
     public void testGetAttributionWhenEmpty() {
         WorkSource ws = new WorkSource();
         assertEquals(-1, ws.getAttributionUid());
@@ -374,6 +403,7 @@
         assertNull(wc.getAttributionTag());
     }
 
+    @Test
     public void testGetAttributionTag() {
         WorkSource ws1 = new WorkSource();
         WorkChain wc = ws1.createWorkChain();
@@ -383,6 +413,7 @@
         assertEquals("tag", wc.getAttributionTag());
     }
 
+    @Test
     public void testRemove_fromChainedWorkSource() {
         WorkSource ws1 = new WorkSource();
         ws1.createWorkChain().addNode(50, "foo");
@@ -403,6 +434,7 @@
         assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid());
     }
 
+    @Test
     public void testRemove_fromSameWorkSource() {
         WorkSource ws1 = new WorkSource(50, "foo");
         WorkSource ws2 = ws1;
@@ -414,6 +446,7 @@
         assertEquals("foo", ws1.getPackageName(0));
     }
 
+    @Test
     public void testTransferWorkChains() {
         WorkSource ws1 = new WorkSource();
         WorkChain wc1 = ws1.createWorkChain().addNode(100, "tag");
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 7c4136d..9300d1e 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -831,6 +831,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 299483542)
     public void onPropertiesChangedListener_setPropertiesCallback() {
         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
index 1df1090..1c72185 100644
--- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
@@ -37,6 +37,7 @@
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testAddAll() {
         final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>();
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 8207c9e..b70f290 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -29,7 +29,9 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.SystemClock;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 
@@ -39,6 +41,8 @@
 import com.android.internal.os.BinderInternal.CallSession;
 
 import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -55,13 +59,22 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
+@IgnoreUnderRavenwood(blockedBy = BinderCallsStats.class)
 public class BinderCallsStatsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private static final int WORKSOURCE_UID = Process.FIRST_APPLICATION_UID;
     private static final int CALLING_UID = 2;
     private static final int REQUEST_SIZE = 2;
     private static final int REPLY_SIZE = 3;
     private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true);
-    private final TestHandler mHandler = new TestHandler();
+    private TestHandler mHandler;
+
+    @Before
+    public void setUp() {
+        mHandler = new TestHandler();
+    }
 
     @Test
     public void testDetailedOff() {
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
index e4597b5..a1b80d2 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
@@ -16,11 +16,19 @@
 
 package com.android.internal.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.os.Binder;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -29,7 +37,8 @@
 /**
  * Tests for {@link BinderCallHeavyHitterWatcher}.
  */
-public final class BinderHeavyHitterTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public final class BinderHeavyHitterTest {
 
     private boolean mListenerNotified = false;
 
@@ -114,6 +123,7 @@
         }
     }
 
+    @Test
     public void testPositive() throws Exception {
         BinderCallHeavyHitterWatcher watcher = BinderCallHeavyHitterWatcher.getInstance();
         try {
@@ -142,6 +152,7 @@
         }
     }
 
+    @Test
     public void testNegative() throws Exception {
         BinderCallHeavyHitterWatcher watcher = BinderCallHeavyHitterWatcher.getInstance();
         try {
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 7bd53b9..31b55e6 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -23,7 +23,9 @@
 import static org.junit.Assert.assertEquals;
 
 import android.os.Binder;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArrayMap;
 import android.util.proto.ProtoOutputStream;
 
@@ -36,6 +38,7 @@
 import com.android.internal.os.BinderLatencyProto.Dims;
 import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -47,7 +50,11 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
+@IgnoreUnderRavenwood(blockedBy = BinderLatencyObserver.class)
 public class BinderLatencyObserverTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void testLatencyCollectionWithMultipleClasses() {
         TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
index e9f6450..5f02f04 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
@@ -20,11 +20,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
-import android.content.Context;
 import android.os.FileUtils;
 import android.util.IntArray;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -102,9 +100,8 @@
     private boolean mHasError;
 
     @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE);
+    public void setUp() throws Exception {
+        mStatsDirectory = Files.createTempDirectory("BinderfsStatsReaderTest").toFile();
         mFreezerBinderAsyncThreshold = 1024;
         mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4);
         mStatsPids = new IntArray();
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
index 7f054d1..2da3873 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
@@ -16,11 +16,8 @@
 
 package com.android.internal.os;
 
-import static androidx.test.InstrumentationRegistry.getContext;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
 import android.os.FileUtils;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -31,6 +28,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 
 @RunWith(AndroidJUnit4.class)
 public class CpuScalingPolicyReaderTest {
@@ -38,7 +36,7 @@
 
     @Before
     public void setup() throws IOException {
-        File testDir = getContext().getDir("test", Context.MODE_PRIVATE);
+        File testDir = Files.createTempDirectory("CpuScalingPolicyReaderTest").toFile();
         FileUtils.deleteContents(testDir);
 
         File policy0 = new File(testDir, "policy0");
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugTest.java b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
index 2a8a857..4371f26 100644
--- a/core/tests/coretests/src/com/android/internal/os/DebugTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
@@ -16,14 +16,22 @@
 
 package com.android.internal.os;
 
+import static org.junit.Assert.assertTrue;
+
 import android.os.Debug;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
 
 @SmallTest
-public class DebugTest extends TestCase {
+@IgnoreUnderRavenwood(reason = "Requires ART support")
+public class DebugTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private final static String EXPECTED_GET_CALLER =
             "com\\.android\\.internal\\.os\\.DebugTest\\.testGetCaller:\\d\\d";
@@ -39,6 +47,7 @@
         return Debug.getCaller();
     }
 
+    @Test
     public void testGetCaller() {
         assertTrue(callDepth0().matches(EXPECTED_GET_CALLER));
     }
@@ -62,6 +71,7 @@
         return callDepth2();
     }
 
+    @Test
     public void testGetCallers() {
         assertTrue(callDepth1().matches(EXPECTED_GET_CALLERS));
     }
@@ -69,6 +79,7 @@
     /**
      * Regression test for b/31943543. Note: must be run under CheckJNI to detect the issue.
      */
+    @Test
     public void testGetMemoryInfo() {
         Debug.MemoryInfo info = new Debug.MemoryInfo();
         Debug.getMemoryInfo(-1, info);
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index cbd2ba4..1d8628d 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.os.FileUtils;
 import android.os.SystemClock;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -31,6 +33,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -57,7 +60,11 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuProcStringReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mRoot;
     private File mTestDir;
     private File mTestFile;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
index b45f8d2..a57a400 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
@@ -24,13 +24,16 @@
 
 import static java.util.stream.Collectors.toList;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 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;
@@ -44,7 +47,10 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuThreadReaderDiffTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private MockitoSession mMockingSessions;
     @Mock KernelCpuThreadReader mMockReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
index d43989c..8c5e3d0 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
@@ -22,6 +22,8 @@
 
 import android.os.Process;
 import android.os.SystemClock;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -29,6 +31,7 @@
 import com.android.internal.os.KernelCpuThreadReader.ProcessCpuUsage;
 import com.android.internal.os.KernelCpuThreadReader.ThreadCpuUsage;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -47,7 +50,10 @@
  */
 @RunWith(AndroidJUnit4.class)
 @LargeTest
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuThreadReaderEndToEndTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private static final int TIMED_NUM_SAMPLES = 5;
     private static final int TIMED_START_MILLIS = 500;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index c3e4014..7eac2a3 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -25,7 +25,9 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -33,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -48,7 +51,11 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuThreadReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mProcDirectory;
 
     @Before
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
index 2ccd74e..d35e0fc 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -21,6 +21,8 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
 
@@ -31,6 +33,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,7 +54,11 @@
  */
 @SmallTest
 @RunWith(Parameterized.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuUidActiveTimeReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidActiveTimeReader mReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
index bda21c6..610e6ae 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -23,11 +23,15 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 
 import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
@@ -50,7 +54,11 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuUidBpfMapReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private Random mRand = new Random(12345);
     private KernelCpuUidTestBpfMapReader mReader;
 
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
index a0dab28..8807de0 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -23,6 +23,8 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -32,6 +34,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,7 +55,11 @@
  */
 @SmallTest
 @RunWith(Parameterized.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuUidClusterTimeReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidClusterTimeReader mReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index 783f264..b730344 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -24,6 +24,8 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -33,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -55,7 +58,11 @@
  */
 @SmallTest
 @RunWith(Parameterized.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuUidFreqTimeReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidFreqTimeReader mReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 1da1a90..6507226 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -23,6 +23,8 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -33,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,7 +52,11 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelCpuUidUserSysTimeReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
index 60dac85..ad5186e 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
@@ -16,12 +16,18 @@
 
 package com.android.internal.os;
 
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.LongSparseLongArray;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
-
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
 import java.io.BufferedReader;
@@ -29,12 +35,17 @@
 /**
  * Tests for KernelMemoryBandwidthStats parsing and delta calculation, based on memory_state_time.
  */
-public class KernelMemoryBandwidthStatsTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
+public class KernelMemoryBandwidthStatsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     /**
      * Standard example of parsing stats.
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testParseStandard() throws Exception {
         KernelMemoryBandwidthStats stats = new KernelMemoryBandwidthStats();
@@ -68,6 +79,7 @@
      * zero.
      * @throws Exception
      */
+    @Test
     @SmallTest
     public void testParseBackwards() throws Exception {
         KernelMemoryBandwidthStats zeroStats = new KernelMemoryBandwidthStats();
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index 2de800b..f42d26d 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -19,9 +19,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -31,7 +35,10 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelSingleProcessCpuThreadReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
     public void getProcessCpuUsage() throws IOException {
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 74ab644..120a4de 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -30,6 +32,7 @@
 import com.android.internal.os.KernelSingleUidTimeReader.Injector;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -43,7 +46,11 @@
 
 @SmallTest
 @RunWith(Parameterized.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class KernelSingleUidTimeReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private final static int TEST_UID = 2222;
     private final static int TEST_FREQ_COUNT = 5;
 
diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
index cb8a62c..632dce0 100644
--- a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
@@ -16,9 +16,13 @@
 
 package com.android.internal.os;
 
-import androidx.test.filters.Suppress;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -28,11 +32,12 @@
 import java.util.List;
 
 // this test causes a IllegalAccessError: superclass not accessible
-@Suppress
-public class LoggingPrintStreamTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class LoggingPrintStreamTest {
 
     TestPrintStream out = new TestPrintStream();
 
+    @Test
     public void testPrintException() {
         @SuppressWarnings("ThrowableInstanceNeverThrown")
         Throwable t = new Throwable("Ignore me.");
@@ -47,6 +52,7 @@
         assertEquals(Arrays.asList(lines), out.lines);
     }
 
+    @Test
     public void testPrintObject() {
         Object o = new Object();
         out.print(4);
@@ -56,6 +62,7 @@
         assertEquals(Arrays.asList("4" + o + "2"), out.lines);
     }
 
+    @Test
     public void testPrintlnObject() {
         Object o = new Object();
         out.print(4);
@@ -65,6 +72,7 @@
         assertEquals(Arrays.asList("4" + o, "2"), out.lines);
     }
 
+    @Test
     public void testPrintf() {
         out.printf("Name: %s\nEmployer: %s", "Bob", "Google");
         assertEquals(Arrays.asList("Name: Bob"), out.lines);
@@ -72,6 +80,7 @@
         assertEquals(Arrays.asList("Name: Bob", "Employer: Google"), out.lines);
     }
 
+    @Test
     public void testPrintInt() {
         out.print(4);
         out.print(2);
@@ -80,12 +89,14 @@
         assertEquals(Collections.singletonList("42"), out.lines);
     }
 
+    @Test
     public void testPrintlnInt() {
         out.println(4);
         out.println(2);
         assertEquals(Arrays.asList("4", "2"), out.lines);
     }
 
+    @Test
     public void testPrintCharArray() {
         out.print("Foo\nBar\nTee".toCharArray());
         assertEquals(Arrays.asList("Foo", "Bar"), out.lines);
@@ -93,6 +104,7 @@
         assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines);
     }
 
+    @Test
     public void testPrintString() {
         out.print("Foo\nBar\nTee");
         assertEquals(Arrays.asList("Foo", "Bar"), out.lines);
@@ -100,22 +112,26 @@
         assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines);
     }
 
+    @Test
     public void testPrintlnCharArray() {
         out.println("Foo\nBar\nTee".toCharArray());
         assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines);
     }
 
+    @Test
     public void testPrintlnString() {
         out.println("Foo\nBar\nTee");
         assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines);
     }
 
+    @Test
     public void testPrintlnStringWithBufferedData() {
         out.print(5);
         out.println("Foo\nBar\nTee");
         assertEquals(Arrays.asList("5Foo", "Bar", "Tee"), out.lines);
     }
 
+    @Test
     public void testAppend() {
         out.append("Foo\n")
             .append('4')
@@ -125,6 +141,7 @@
         assertEquals(Arrays.asList("Foo", "4", "a"), out.lines);
     }
 
+    @Test
     public void testMultiByteCharactersSpanningBuffers() throws Exception {
         // assume 3*1000 bytes won't fit in LoggingPrintStream's internal buffer
         StringBuilder builder = new StringBuilder();
@@ -138,6 +155,7 @@
         assertEquals(Arrays.asList(expected), out.lines);
     }
 
+    @Test
     public void testWriteOneByteAtATimeMultibyteCharacters() throws Exception {
         String expected = " \u20AC  \u20AC   \u20AC    \u20AC     ";
         for (byte b : expected.getBytes()) {
@@ -147,6 +165,7 @@
         assertEquals(Arrays.asList(expected), out.lines);
     }
 
+    @Test
     public void testWriteByteArrayAtATimeMultibyteCharacters() throws Exception {
         String expected = " \u20AC  \u20AC   \u20AC    \u20AC     ";
         out.write(expected.getBytes());
@@ -154,6 +173,7 @@
         assertEquals(Arrays.asList(expected), out.lines);
     }
 
+    @Test
     public void testWriteWithOffsetsMultibyteCharacters() throws Exception {
         String expected = " \u20AC  \u20AC   \u20AC    \u20AC     ";
         byte[] bytes = expected.getBytes();
@@ -167,6 +187,7 @@
         assertEquals(Arrays.asList(expected), out.lines);
     }
 
+    @Test
     public void testWriteFlushesOnNewlines() throws Exception {
         String a = " \u20AC  \u20AC ";
         String b = "  \u20AC    \u20AC  ";
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index faccf1a..c9536b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -22,16 +22,22 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = LongArrayMultiStateCounter.class)
 public class LongArrayMultiStateCounterTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
     public void setStateAndUpdateValue() {
@@ -51,6 +57,16 @@
     }
 
     @Test
+    public void setValue() {
+        LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+        counter.setValues(0, new long[]{1, 2, 3, 4});
+        counter.setValues(1, new long[]{5, 6, 7, 8});
+        assertCounts(counter, 0, new long[]{1, 2, 3, 4});
+        assertCounts(counter, 1, new long[]{5, 6, 7, 8});
+    }
+
+    @Test
     public void setEnabled() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index 3413753..e064e74 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -22,16 +22,22 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = LongMultiStateCounterTest.class)
 public class LongMultiStateCounterTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
     public void setStateAndUpdateValue() {
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index 48baf09..dfb5cc3 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -23,6 +23,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -30,6 +31,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +42,9 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public final class LooperStatsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private HandlerThread mThreadFirst;
     private HandlerThread mThreadSecond;
     private Handler mHandlerFirst;
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 7951270..0742052 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -43,14 +43,12 @@
         assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
 
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        monotonicClock.writeXml(out, Xml.newFastSerializer());
-        String xml = out.toString();
-        assertThat(xml).contains("timeshift=\"1234\"");
+        monotonicClock.writeXml(out, Xml.newBinarySerializer());
 
         mClock.realtime = 42;
         MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
-        newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
-                Xml.newFastPullParser());
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
 
         mClock.realtime = 2000;
         assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 77202d1..c0f0714 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -27,6 +27,8 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -38,6 +40,7 @@
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -50,7 +53,10 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = PowerProfile.class)
 public class PowerProfileTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     static final String TAG_TEST_MODEM = "test-modem";
     static final String ATTR_NAME = "name";
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 29da231..b99e202 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -21,17 +21,23 @@
 import android.os.BatteryConsumer;
 import android.os.Parcel;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class PowerStatsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private PowerStats.DescriptorRegistry mRegistry;
     private PowerStats.Descriptor mDescriptor;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index c3d40eb..f61fc7c 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -18,11 +18,9 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.content.Context;
 import android.os.FileUtils;
 import android.util.IntArray;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -45,9 +43,8 @@
     private ArrayList<int[]> mPids = new ArrayList<>();
 
     @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    public void setUp() throws Exception {
+        mProcDirectory = Files.createTempDirectory("ProcLocksReaderTest").toFile();
     }
 
     @After
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
index e97caf8..3e4f34d 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
@@ -18,10 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.content.Context;
 import android.os.FileUtils;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -41,9 +39,8 @@
     private File mProcDirectory;
 
     @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    public void setUp() throws Exception {
+        mProcDirectory = Files.createTempDirectory("ProcStatsUtilTest").toFile();
     }
 
     @After
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
index 9db3f8a..a706350 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
@@ -20,15 +20,16 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
-import android.content.Context;
 import android.os.FileUtils;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,14 +40,16 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ProcTimeInStateReader.class)
 public class ProcTimeInStateReaderTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private File mProcDirectory;
 
     @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    public void setUp() throws Exception {
+        mProcDirectory = Files.createTempDirectory("ProcTimeInStateReaderTest").toFile();
     }
 
     @After
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
index 81cc9d8..d11c500 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
@@ -18,15 +18,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @SmallTest
 @RunWith(JUnit4.class)
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class ProcessCpuTrackerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void testGetCpuTime() throws Exception {
         final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
index 85eafc5..84c93c2 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -20,10 +20,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
-import android.content.Context;
 import android.os.FileUtils;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -48,7 +46,6 @@
 @RunWith(AndroidJUnit4.class)
 public class StoragedUidIoStatsReaderTest {
 
-    private File mRoot;
     private File mTestDir;
     private File mTestFile;
     // private Random mRand = new Random();
@@ -57,15 +54,10 @@
     @Mock
     private StoragedUidIoStatsReader.Callback mCallback;
 
-    private Context getContext() {
-        return InstrumentationRegistry.getContext();
-    }
-
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
-        mRoot = getContext().getFilesDir();
+        mTestDir = Files.createTempDirectory("StoragedUidIoStatsReaderTest").toFile();
         mTestFile = new File(mTestDir, "test.file");
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(mTestFile.getAbsolutePath());
     }
@@ -73,10 +65,8 @@
     @After
     public void tearDown() throws Exception {
         FileUtils.deleteContents(mTestDir);
-        FileUtils.deleteContents(mRoot);
     }
 
-
     /**
      * Tests that reading will never call the callback.
      */
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 916e2b5..6bdc06a 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -63,8 +63,13 @@
         createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
         installDecor();
 
-        assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
-                is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
+        if (mPhoneWindow.mDefaultEdgeToEdge && !mPhoneWindow.isFloating()) {
+            assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+                    is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
+        } else {
+            assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+                    is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
+        }
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
index e09cfd2..ae2ef0cb 100644
--- a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
@@ -34,10 +34,13 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -46,7 +49,10 @@
  * Test class for {@link EnergyConsumerStats}.
  */
 @SmallTest
+@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class EnergyConsumerStatsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
     public void testConstruction() {
diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
index e8246c8..ac659e1 100644
--- a/core/tests/utiltests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
@@ -18,8 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -29,6 +33,9 @@
 
 @RunWith(AndroidJUnit4.class)
 public class TimeUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     public static final long SECOND_IN_MILLIS = 1000;
     public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
     public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
@@ -78,6 +85,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testDumpTime() {
         assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> {
             TimeUtils.dumpTime(pw, 1672556400000L);
@@ -91,6 +99,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testFormatForLogging() {
         assertEquals("unknown", TimeUtils.formatForLogging(0));
         assertEquals("unknown", TimeUtils.formatForLogging(-1));
@@ -99,6 +108,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testLogTimeOfDay() {
         assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L));
     }
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index f0ed6ee..e346b51 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
 xutan@google.com
 
 # Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b1b196d..fe65fdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -168,6 +169,13 @@
     private final Object mLock = new Object();
     private StartingWindowController mStartingWindow;
 
+    /** Overlay surface for home root task */
+    private final SurfaceControl mHomeTaskOverlayContainer = new SurfaceControl.Builder()
+            .setName("home_task_overlay_container")
+            .setContainerLayer()
+            .setHidden(false)
+            .build();
+
     /**
      * In charge of showing compat UI. Can be {@code null} if the device doesn't support size
      * compat or if this isn't the main {@link ShellTaskOrganizer}.
@@ -428,6 +436,14 @@
         }
     }
 
+    /**
+     * Returns a surface which can be used to attach overlays to the home root task
+     */
+    @NonNull
+    public SurfaceControl getHomeTaskOverlayContainer() {
+        return mHomeTaskOverlayContainer;
+    }
+
     @Override
     public void addStartingWindow(StartingWindowInfo info) {
         if (mStartingWindow != null) {
@@ -485,6 +501,15 @@
         if (mUnfoldAnimationController != null) {
             mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash());
         }
+
+        if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+            ProtoLog.v(WM_SHELL_TASK_ORG, "Adding overlay to home task");
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.setLayer(mHomeTaskOverlayContainer, Integer.MAX_VALUE);
+            t.reparent(mHomeTaskOverlayContainer, info.getLeash());
+            t.apply();
+        }
+
         notifyLocusVisibilityIfNeeded(info.getTaskInfo());
         notifyCompatUI(info.getTaskInfo(), listener);
         mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
@@ -579,6 +604,12 @@
             notifyCompatUI(taskInfo, null /* taskListener */);
             // Notify the recent tasks that a task has been removed
             mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                t.reparent(mHomeTaskOverlayContainer, null);
+                t.apply();
+                ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface");
+            }
 
             if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
                 // Preemptively clean up the leash only if shell transitions are not enabled
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 5843635..cf858dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.back;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
+import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -221,6 +222,7 @@
 
     private void onInit() {
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+        updateEnableAnimationFromFlags();
         createAdapter();
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
@@ -229,28 +231,39 @@
     private void setupAnimationDeveloperSettingsObserver(
             @NonNull ContentResolver contentResolver,
             @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
+        if (predictiveBackSystemAnimations()) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+                    + "developer settings flag is ignored and no content observer registered");
+            return;
+        }
         ContentObserver settingsObserver = new ContentObserver(backgroundHandler) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
-                updateEnableAnimationFromSetting();
+                updateEnableAnimationFromFlags();
             }
         };
         contentResolver.registerContentObserver(
                 Global.getUriFor(Global.ENABLE_BACK_ANIMATION),
                 false, settingsObserver, UserHandle.USER_SYSTEM
         );
-        updateEnableAnimationFromSetting();
     }
 
+    /**
+     * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the
+     * aconfig flag and the developer settings flag
+     */
     @ShellBackgroundThread
-    private void updateEnableAnimationFromSetting() {
-        int settingValue = Global.getInt(mContext.getContentResolver(),
-                Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
-        boolean isEnabled = settingValue == SETTING_VALUE_ON;
+    private void updateEnableAnimationFromFlags() {
+        boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
         mEnableAnimations.set(isEnabled);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
 
+    private boolean isDeveloperSettingEnabled() {
+        return Global.getInt(mContext.getContentResolver(),
+                Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+    }
+
     public BackAnimation getBackAnimationImpl() {
         return mBackAnimation;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 689323b..7f34ee0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -78,34 +78,37 @@
         mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+                BubbleBarExpandedView bbev = getExpandedView();
+                if (bbev != null) {
                     // We need to be Z ordered on top in order for alpha animations to work.
-                    mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true);
-                    mExpandedBubble.getBubbleBarExpandedView().setAnimating(true);
+                    bbev.setSurfaceZOrderedOnTop(true);
+                    bbev.setAnimating(true);
                 }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+                BubbleBarExpandedView bbev = getExpandedView();
+                if (bbev != null) {
                     // The surface needs to be Z ordered on top for alpha values to work on the
                     // TaskView, and if we're temporarily hidden, we are still on the screen
                     // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
                     // = 0f remains in effect.
                     if (mIsExpanded) {
-                        mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false);
+                        bbev.setSurfaceZOrderedOnTop(false);
                     }
 
-                    mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded);
-                    mExpandedBubble.getBubbleBarExpandedView().setAnimating(false);
+                    bbev.setContentVisibility(mIsExpanded);
+                    bbev.setAnimating(false);
                 }
             }
         });
         mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
-            if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+            BubbleBarExpandedView bbev = getExpandedView();
+            if (bbev != null) {
                 float alpha = (float) valueAnimator.getAnimatedValue();
-                mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha);
-                mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha);
+                bbev.setTaskViewAlpha(alpha);
+                bbev.setAlpha(alpha);
             }
         });
     }
@@ -116,11 +119,8 @@
     public void animateExpansion(BubbleViewProvider expandedBubble,
             @Nullable Runnable afterAnimation) {
         mExpandedBubble = expandedBubble;
-        if (mExpandedBubble == null) {
-            return;
-        }
-        BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView();
-        if (bev == null) {
+        final BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
             return;
         }
         mIsExpanded = true;
@@ -129,11 +129,11 @@
         mExpandedViewContainerMatrix.setScaleY(0f);
 
         updateExpandedView();
-        bev.setAnimating(true);
-        bev.setContentVisibility(false);
-        bev.setAlpha(0f);
-        bev.setTaskViewAlpha(0f);
-        bev.setVisibility(VISIBLE);
+        bbev.setAnimating(true);
+        bbev.setContentVisibility(false);
+        bbev.setAlpha(0f);
+        bbev.setTaskViewAlpha(0f);
+        bbev.setVisibility(VISIBLE);
 
         // Set the pivot point for the scale, so the view animates out from the bubble bar.
         Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
@@ -143,7 +143,7 @@
                 bubbleBarPosition.x,
                 bubbleBarPosition.y);
 
-        bev.setAnimationMatrix(mExpandedViewContainerMatrix);
+        bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
 
         mExpandedViewAlphaAnimator.start();
 
@@ -156,13 +156,12 @@
                         AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
                         mScaleInSpringConfig)
                 .addUpdateListener((target, values) -> {
-                    mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
-                            mExpandedViewContainerMatrix);
+                    bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
                 })
                 .withEndActions(() -> {
-                    bev.setAnimationMatrix(null);
+                    bbev.setAnimationMatrix(null);
                     updateExpandedView();
-                    bev.setSurfaceZOrderedOnTop(false);
+                    bbev.setSurfaceZOrderedOnTop(false);
                     if (afterAnimation != null) {
                         afterAnimation.run();
                     }
@@ -177,7 +176,8 @@
      */
     public void animateCollapse(Runnable endRunnable) {
         mIsExpanded = false;
-        if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+        final BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
             Log.w(TAG, "Trying to animate collapse without a bubble");
             return;
         }
@@ -196,17 +196,10 @@
                                 EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT),
                         mScaleOutSpringConfig)
                 .addUpdateListener((target, values) -> {
-                    if (mExpandedBubble != null
-                            && mExpandedBubble.getBubbleBarExpandedView() != null) {
-                        mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
-                                mExpandedViewContainerMatrix);
-                    }
+                    bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
                 })
                 .withEndActions(() -> {
-                    if (mExpandedBubble != null
-                            && mExpandedBubble.getBubbleBarExpandedView() != null) {
-                        mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null);
-                    }
+                    bbev.setAnimationMatrix(null);
                     if (endRunnable != null) {
                         endRunnable.run();
                     }
@@ -223,12 +216,20 @@
         mExpandedViewAlphaAnimator.cancel();
     }
 
+    private @Nullable BubbleBarExpandedView getExpandedView() {
+        BubbleViewProvider bubble = mExpandedBubble;
+        if (bubble != null) {
+            return bubble.getBubbleBarExpandedView();
+        }
+        return null;
+    }
+
     private void updateExpandedView() {
-        if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+        BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
             Log.w(TAG, "Trying to update the expanded view without a bubble");
             return;
         }
-        BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
 
         boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index ef763ec..afd3b14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.LayoutInflater;
@@ -227,9 +228,12 @@
     }
 
     private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+        final Intent intent = taskInfo.baseIntent;
         return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
                 && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
                     || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+                && Intent.ACTION_MAIN.equals(intent.getAction())
+                && intent.hasCategory(Intent.CATEGORY_LAUNCHER)
                 && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index deb7c6d..1385f42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -1,3 +1,7 @@
 # WM shell sub-module desktop owners
 atsjenk@google.com
+jorgegil@google.com
 madym@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index a3803ed..8a0eea0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -2,3 +2,6 @@
 atsjenk@google.com
 jorgegil@google.com
 madym@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 8eb4a5a..f5c01d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -770,6 +770,7 @@
                     }
                     if (mContentOverlay != null) {
                         mContentOverlay.onAnimationEnd(tx, destBounds);
+                        clearContentOverlay();
                     }
                 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 850b06a0..a2bd47c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -109,7 +109,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Do nothing. Color overlay should be fully opaque by now.
+            // Do nothing. Color overlay should be fully opaque by now, ready for fade out.
         }
 
         private float[] getContentOverlayColor(Context context) {
@@ -167,7 +167,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            atomicTx.remove(mLeash);
+            // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out.
         }
     }
 
@@ -193,19 +193,12 @@
                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
 
-            final int appWidth = appBounds.width();
-            final int appHeight = appBounds.height();
-
-            // In order to have the overlay always cover the pip window during the transition, the
-            // overlay will be drawn with the max size of the start and end bounds in different
-            // rotation.
-            final int overlaySize = Math.max(Math.max(appWidth, appHeight),
-                    Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+            final int overlaySize = getOverlaySize(appBounds, destinationBounds);
             mOverlayHalfSize = overlaySize >> 1;
 
             // When the activity is in the secondary split, make sure the scaling center is not
             // offset.
-            mAppBounds = new Rect(0, 0, appWidth, appHeight);
+            mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
 
             mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
             prepareAppIconOverlay(appIcon);
@@ -215,6 +208,21 @@
                     .build();
         }
 
+        /**
+         * Returns the size of the app icon overlay.
+         *
+         * In order to have the overlay always cover the pip window during the transition,
+         * the overlay will be drawn with the max size of the start and end bounds in different
+         * rotation.
+         */
+        public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
+            final int appWidth = appBounds.width();
+            final int appHeight = appBounds.height();
+
+            return Math.max(Math.max(appWidth, appHeight),
+                    Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+        }
+
         @Override
         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
             tx.show(mLeash);
@@ -248,7 +256,7 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            atomicTx.remove(mLeash);
+            // Do nothing. Icon overlay should be fully opaque by now, ready for fade out.
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c1164fc..743b1ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -156,74 +156,77 @@
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
             new PipAnimationController.PipAnimationCallback() {
-        private boolean mIsCancelled;
-        @Override
-        public void onPipAnimationStart(TaskInfo taskInfo,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            mIsCancelled = false;
-            sendOnPipTransitionStarted(direction);
-        }
+                private boolean mIsCancelled;
 
-        @Override
-        public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            if (mIsCancelled) {
-                sendOnPipTransitionFinished(direction);
-                maybePerformFinishResizeCallback();
-                return;
-            }
-            final int animationType = animator.getAnimationType();
-            final Rect destinationBounds = animator.getDestinationBounds();
-            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay, true /* withStartDelay*/);
-            }
-            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
-                    && direction == TRANSITION_DIRECTION_TO_PIP) {
-                // Notify the display to continue the deferred orientation change.
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.scheduleFinishEnterPip(mToken, destinationBounds);
-                mTaskOrganizer.applyTransaction(wct);
-                // The final task bounds will be applied by onFixedRotationFinished so that all
-                // coordinates are in new rotation.
-                mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
-                mDeferredAnimEndTransaction = tx;
-                return;
-            }
-            final boolean isExitPipDirection = isOutPipDirection(direction)
-                    || isRemovePipDirection(direction);
-            if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
-                    || isExitPipDirection) {
-                // execute the finish resize callback if needed after the transaction is committed
-                tx.addTransactionCommittedListener(mMainExecutor,
-                        PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+                @Override
+                public void onPipAnimationStart(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    mIsCancelled = false;
+                    sendOnPipTransitionStarted(direction);
+                }
 
-                // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
-                // the end of an exit PIP animation.
-                // This is necessary in case there was a resize animation ongoing when exit PIP
-                // started, in which case the first resize will be skipped to let the exit
-                // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResizeDelayedIfNeeded(() -> {
-                    finishResize(tx, destinationBounds, direction, animationType);
-                    sendOnPipTransitionFinished(direction);
-                });
-            }
-        }
+                @Override
+                public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    if (mIsCancelled) {
+                        sendOnPipTransitionFinished(direction);
+                        maybePerformFinishResizeCallback();
+                        return;
+                    }
+                    final int animationType = animator.getAnimationType();
+                    final Rect destinationBounds = animator.getDestinationBounds();
+                    if (isInPipDirection(direction) && mPipOverlay != null) {
+                        fadeOutAndRemoveOverlay(mPipOverlay,
+                                null /* callback */, true /* withStartDelay*/);
+                    }
+                    if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
+                            && direction == TRANSITION_DIRECTION_TO_PIP) {
+                        // Notify the display to continue the deferred orientation change.
+                        final WindowContainerTransaction wct = new WindowContainerTransaction();
+                        wct.scheduleFinishEnterPip(mToken, destinationBounds);
+                        mTaskOrganizer.applyTransaction(wct);
+                        // The final task bounds will be applied by onFixedRotationFinished so
+                        // that all coordinates are in new rotation.
+                        mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
+                        mDeferredAnimEndTransaction = tx;
+                        return;
+                    }
+                    final boolean isExitPipDirection = isOutPipDirection(direction)
+                            || isRemovePipDirection(direction);
+                    if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+                            || isExitPipDirection) {
+                        // execute the finish resize callback if needed after the transaction is
+                        // committed
+                        tx.addTransactionCommittedListener(mMainExecutor,
+                                PipTaskOrganizer.this::maybePerformFinishResizeCallback);
 
-        @Override
-        public void onPipAnimationCancel(TaskInfo taskInfo,
-                PipAnimationController.PipTransitionAnimator animator) {
-            final int direction = animator.getTransitionDirection();
-            mIsCancelled = true;
-            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay, true /* withStartDelay */);
-            }
-            sendOnPipTransitionCancelled(direction);
-        }
-    };
+                        // Finish resize as long as we're not exiting PIP, or, if we are, only if
+                        // this is the end of an exit PIP animation.
+                        // This is necessary in case there was a resize animation ongoing when
+                        // exit PIP started, in which case the first resize will be skipped to
+                        // let the exit operation handle the final resize out of PIP mode.
+                        // See b/185306679.
+                        finishResizeDelayedIfNeeded(() -> {
+                            finishResize(tx, destinationBounds, direction, animationType);
+                            sendOnPipTransitionFinished(direction);
+                        });
+                    }
+                }
+
+                @Override
+                public void onPipAnimationCancel(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    mIsCancelled = true;
+                    if (isInPipDirection(direction) && mPipOverlay != null) {
+                        fadeOutAndRemoveOverlay(mPipOverlay,
+                                null /* callback */, true /* withStartDelay */);
+                    }
+                    sendOnPipTransitionCancelled(direction);
+                }
+            };
 
     /**
      * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
@@ -327,11 +330,18 @@
 
     /**
      * An optional overlay used to mask content changing between an app in/out of PiP, only set if
-     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
+     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav.
      */
     @Nullable
     SurfaceControl mSwipePipToHomeOverlay;
 
+    /**
+     * An optional overlay used to mask content changing between an app in/out of PiP, only set if
+     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false.
+     */
+    @Nullable
+    SurfaceControl mPipOverlay;
+
     public PipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
@@ -1766,6 +1776,7 @@
                     animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
                 }
             }
+            mPipOverlay = animator.getContentOverlayLeash();
             // The destination bounds are used for the end rect of animation and the final bounds
             // after animation finishes. So after the animation is started, the destination bounds
             // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
@@ -1879,6 +1890,14 @@
     }
 
     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+        if (mPipOverlay != null) {
+            if (mPipOverlay != surface) {
+                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "%s: trying to remove overlay (%s) which is not local reference (%s)",
+                        TAG, surface, mPipOverlay);
+            }
+            mPipOverlay = null;
+        }
         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
             // Avoid double removal, which is fatal.
             return;
@@ -1907,11 +1926,11 @@
     private void cancelCurrentAnimator() {
         final PipAnimationController.PipTransitionAnimator<?> animator =
                 mPipAnimationController.getCurrentAnimator();
+        // remove any overlays if present
+        if (mPipOverlay != null) {
+            removeContentOverlay(mPipOverlay, null /* callback */);
+        }
         if (animator != null) {
-            if (animator.getContentOverlayLeash() != null) {
-                removeContentOverlay(animator.getContentOverlayLeash(),
-                        animator::clearContentOverlay);
-            }
             PipAnimationController.quietCancel(animator);
             mPipAnimationController.resetAnimatorState();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index fe4980a..0f3c162 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -43,6 +43,7 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 
 import android.animation.Animator;
+import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -76,6 +77,8 @@
 import com.android.wm.shell.util.TransitionUtil;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 
 /**
@@ -86,6 +89,18 @@
 
     private static final String TAG = PipTransition.class.getSimpleName();
 
+    /** No fixed rotation, or fixed rotation state is undefined. */
+    private static final int FIXED_ROTATION_UNDEFINED = 0;
+    /**
+     * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome());
+     * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to
+     * see the fixed rotation.
+     */
+    private static final int FIXED_ROTATION_CALLBACK = 1;
+
+    /** Fixed rotation detected in the incoming transition. */
+    private static final int FIXED_ROTATION_TRANSITION = 2;
+
     private final Context mContext;
     private final PipTransitionState mPipTransitionState;
     private final PipDisplayLayoutState mPipDisplayLayoutState;
@@ -106,17 +121,28 @@
     /** The Task window that is currently in PIP windowing mode. */
     @Nullable
     private WindowContainerToken mCurrentPipTaskToken;
-    /** Whether display is in fixed rotation. */
-    private boolean mInFixedRotation;
+
+    @IntDef(prefix = { "FIXED_ROTATION_" }, value =  {
+            FIXED_ROTATION_UNDEFINED,
+            FIXED_ROTATION_CALLBACK,
+            FIXED_ROTATION_TRANSITION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FixedRotationState {}
+
+    /** Fixed rotation state of the display. */
+    private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED;
     /**
      * The rotation that the display will apply after expanding PiP to fullscreen. This is only
-     * meaningful if {@link #mInFixedRotation} is true.
+     * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}.
      */
     @Surface.Rotation
     private int mEndFixedRotation;
     /** Whether the PIP window has fade out for fixed rotation. */
     private boolean mHasFadeOut;
 
+    private Rect mInitBounds = new Rect();
+
     /** Used for setting transform to a transaction from animator. */
     private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
             new PipAnimationController.PipTransactionHandler() {
@@ -181,8 +207,16 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        mInFixedRotation = fixedRotationChange != null;
-        mEndFixedRotation = mInFixedRotation
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+            // If we are just about to process potential fixed rotation information,
+            // then fixed rotation state should either be UNDEFINED or CALLBACK.
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "%s: startAnimation() should start with clear fixed rotation state", TAG);
+            mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+        }
+        mFixedRotationState = fixedRotationChange != null
+                ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
+        mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
                 ? fixedRotationChange.getEndFixedRotation()
                 : ROTATION_UNDEFINED;
 
@@ -347,6 +381,10 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {
+        // Transition either finished pre-emptively, got merged, or aborted,
+        // so update fixed rotation state to default.
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+
         if (transition != mExitTransition) {
             return;
         }
@@ -408,7 +446,8 @@
                 // done at the start. But if it is running fixed rotation, there will be a seamless
                 // display transition later. So the last rotation transform needs to be kept to
                 // avoid flickering, and then the display transition will reset the transform.
-                if (!mInFixedRotation && mFinishTransaction != null) {
+                if (mFixedRotationState != FIXED_ROTATION_TRANSITION
+                        && mFinishTransaction != null) {
                     mFinishTransaction.merge(tx);
                 }
             } else {
@@ -426,12 +465,27 @@
                     mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
+                    if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) {
+                        // Resetting the scale for pinned task while re-adjusting its crop,
+                        // also scales the overlay. So we need to update the overlay leash too.
+                        Rect overlayBounds = new Rect(destinationBounds);
+                        final int overlaySize = PipContentOverlay.PipAppIconOverlay
+                                .getOverlaySize(mInitBounds, destinationBounds);
+
+                        overlayBounds.offsetTo(
+                                (destinationBounds.width() - overlaySize) / 2,
+                                (destinationBounds.height() - overlaySize) / 2);
+                        mSurfaceTransactionHelper.resetScale(tx,
+                                mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds);
+                    }
                 }
+                mInitBounds.setEmpty();
                 wct.setBoundsChangeTransaction(taskInfo.token, tx);
             }
             final int displayRotation = taskInfo.getConfiguration().windowConfiguration
                     .getDisplayRotation();
-            if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
+            if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION
+                    && mEndFixedRotation != displayRotation
                     && hasValidLeash) {
                 // Launcher may update the Shelf height during the animation, which will update the
                 // destination bounds. Because this is in fixed rotation, We need to make sure the
@@ -451,6 +505,8 @@
             mFinishTransaction = null;
             callFinishCallback(wct);
         }
+        // This is the end of transition on the Shell side so update the fixed rotation state.
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
         finishResizeForMenu(destinationBounds);
     }
 
@@ -467,6 +523,7 @@
         // mFinishCallback might be null with an outdated mCurrentPipTaskToken
         // for example, when app crashes while in PiP and exit transition has not started
         mCurrentPipTaskToken = null;
+        mFixedRotationState = FIXED_ROTATION_UNDEFINED;
         if (mFinishCallback == null) return;
         mFinishCallback.onTransitionFinished(null /* wct */);
         mFinishCallback = null;
@@ -475,6 +532,9 @@
 
     @Override
     public void onFixedRotationStarted() {
+        if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) {
+            mFixedRotationState = FIXED_ROTATION_CALLBACK;
+        }
         fadeEnteredPipIfNeed(false /* show */);
     }
 
@@ -656,7 +716,7 @@
 
         // Check if it is fixed rotation.
         final int rotationDelta;
-        if (mInFixedRotation) {
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             final int startRotation = pipChange.getStartRotation();
             final int endRotation = mEndFixedRotation;
             rotationDelta = deltaRotation(startRotation, endRotation);
@@ -873,11 +933,13 @@
         final int startRotation = pipChange.getStartRotation();
         // Check again in case some callers use startEnterAnimation directly so the flag was not
         // set in startAnimation, e.g. from DefaultMixedHandler.
-        if (!mInFixedRotation) {
+        if (mFixedRotationState != FIXED_ROTATION_TRANSITION) {
             mEndFixedRotation = pipChange.getEndFixedRotation();
-            mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+            mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED
+                    ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
         }
-        final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+        final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
+                ? mEndFixedRotation : pipChange.getEndRotation();
 
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                 taskInfo.topActivityInfo);
@@ -888,10 +950,15 @@
 
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
+
+        // Cache the start bounds for overlay manipulations as a part of finishCallback.
+        mInitBounds.set(currentBounds);
+
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
-        if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+        if (rotationDelta != Surface.ROTATION_0
+                && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // Need to get the bounds of new rotation in old rotation for fixed rotation,
             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
                     destinationBounds, sourceHintRect);
@@ -955,10 +1022,12 @@
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
+        mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration);
-        if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+        if (rotationDelta != Surface.ROTATION_0
+                && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // For fixed rotation, the animation destination bounds is in old rotation coordinates.
             // Set the destination bounds to new coordinates after the animation is finished.
             // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
@@ -997,13 +1066,17 @@
             @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
             @NonNull Rect destinationBounds,
             @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
-        if (mInFixedRotation) {
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
             // If rotation changes when returning to home, the transition should contain both the
             // entering PiP and the display change (PipController#startSwipePipToHome has updated
             // the display layout to new rotation). So it is not expected to see fixed rotation.
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
         }
+        Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
+        if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
+            mInitBounds.set(appBounds);
+        }
         final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
         if (swipePipToHomeOverlay != null) {
             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 20c57fa..04911c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -78,9 +78,9 @@
                     if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
                         return;
                     }
-                    if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                        mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                                animator::clearContentOverlay, true /* withStartDelay*/);
+                    if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+                        mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+                                null /* callback */, true /* withStartDelay*/);
                     }
                     onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
                     sendOnPipTransitionFinished(direction);
@@ -90,9 +90,9 @@
                 public void onPipAnimationCancel(TaskInfo taskInfo,
                         PipAnimationController.PipTransitionAnimator animator) {
                     final int direction = animator.getTransitionDirection();
-                    if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
-                        mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
-                                animator::clearContentOverlay, true /* withStartDelay */);
+                    if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+                        mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+                                null /* callback */, true /* withStartDelay */);
                     }
                     sendOnPipTransitionCancelled(animator.getTransitionDirection());
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 56f1c78..7b57097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -847,9 +847,12 @@
                 .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
                 .orElse(null);
         if (taskInfo != null) {
-            startTask(taskInfo.taskId, position, options);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "Start task in background");
+            if (ENABLE_SHELL_TRANSITIONS) {
+                mStageCoordinator.startTask(taskInfo.taskId, position, options);
+            } else {
+                startTask(taskInfo.taskId, position, options);
+            }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
             return;
         }
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
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 be685b5..449bef5 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
@@ -263,6 +263,10 @@
             mStartIntent2 = startIntent2;
             mActivatePosition = position;
         }
+        SplitRequest(int taskId1, int position) {
+            mActivateTaskId = taskId1;
+            mActivatePosition = position;
+        }
         SplitRequest(int taskId1, int taskId2, int position) {
             mActivateTaskId = taskId1;
             mActivateTaskId2 = taskId2;
@@ -556,6 +560,27 @@
         }
     }
 
+    /** Use this method to launch an existing Task via a taskId */
+    void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+        mSplitRequest = new SplitRequest(taskId, position);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+        wct.startTask(taskId, options);
+        // If this should be mixed, send the task to avoid split handle transition directly.
+        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+            mTaskOrganizer.applyTransaction(wct);
+            return;
+        }
+
+        // If split screen is not activated, we're expecting to open a pair of apps to split.
+        final int extraTransitType = mMainStage.isActive()
+                ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+        prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+
+        mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
+                extraTransitType, !mIsDropEntering);
+    }
+
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
@@ -1593,7 +1618,9 @@
             // Ensure to evict old splitting tasks because the new split pair might be composed by
             // one of the splitting tasks, evicting the task when finishing entering transition
             // won't guarantee to put the task to the indicated new position.
-            mMainStage.evictAllChildren(wct);
+            if (!mIsDropEntering) {
+                mMainStage.evictAllChildren(wct);
+            }
             mMainStage.reparentTopTask(wct);
             prepareSplitLayout(wct, resizeAnim);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 43ae5f4..ae21c4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -247,7 +247,7 @@
         }
         params.layoutInDisplayCutoutMode = a.getInt(
                 R.styleable.Window_windowLayoutInDisplayCutoutMode,
-                params.layoutInDisplayCutoutMode);
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
         params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
         a.recycle();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index eb30119..9f20f49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -48,6 +48,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -901,6 +902,18 @@
         return false;
     }
 
+    /** Use to when split use taskId to enter, check if this enter transition should be mixed or
+     * not.*/
+    public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+        // Check if this intent package is same as pip one or not, if true we want let the pip
+        // task enter split.
+        if (mPipHandler != null) {
+            return mPipHandler.isInPipPackage(
+                    SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+        }
+        return false;
+    }
+
     /** @return whether the transition-request represents a pip-entry. */
     public boolean requestHasPipEnter(TransitionRequestInfo request) {
         return mPipHandler.requestHasPipEnter(request);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index 644a6a5..7f4a8f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.transition;
 
+import android.view.SurfaceControl;
 import android.window.RemoteTransition;
 import android.window.TransitionFilter;
 
@@ -42,6 +43,13 @@
      */
     IBinder getShellApplyToken() = 3;
 
-    /** Set listener that will receive callbacks about transitions involving home activity */
+    /**
+     * Set listener that will receive callbacks about transitions involving home activity.
+     */
     oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
+
+    /**
+     * Returns a container surface for the home root task.
+     */
+    SurfaceControl getHomeTaskOverlayContainer() = 5;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b98762d..af69b52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -64,7 +64,6 @@
 import android.window.TransitionMetrics;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 import androidx.annotation.BinderThread;
 
@@ -72,6 +71,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
@@ -172,7 +172,7 @@
     /** Transition to animate task to desktop. */
     public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
 
-    private final WindowOrganizer mOrganizer;
+    private final ShellTaskOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
@@ -264,7 +264,7 @@
     public Transitions(@NonNull Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
-            @NonNull WindowOrganizer organizer,
+            @NonNull ShellTaskOrganizer organizer,
             @NonNull TransactionPool pool,
             @NonNull DisplayController displayController,
             @NonNull ShellExecutor mainExecutor,
@@ -280,7 +280,7 @@
             @NonNull ShellInit shellInit,
             @Nullable ShellCommandHandler shellCommandHandler,
             @NonNull ShellController shellController,
-            @NonNull WindowOrganizer organizer,
+            @NonNull ShellTaskOrganizer organizer,
             @NonNull TransactionPool pool,
             @NonNull DisplayController displayController,
             @NonNull ShellExecutor mainExecutor,
@@ -1240,6 +1240,10 @@
         }
     }
 
+    private SurfaceControl getHomeTaskOverlayContainer() {
+        return mOrganizer.getHomeTaskOverlayContainer();
+    }
+
     /**
      * Interface for a callback that must be called after a TransitionHandler finishes playing an
      * animation.
@@ -1470,6 +1474,17 @@
                                 listener);
                     });
         }
+
+        @Override
+        public SurfaceControl getHomeTaskOverlayContainer() {
+            SurfaceControl[] result = new SurfaceControl[1];
+            executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer",
+                    (controller) -> {
+                        result[0] = controller.getHomeTaskOverlayContainer();
+                    }, true /* blocking */);
+            // Return a copy as writing to parcel releases the original surface
+            return new SurfaceControl(result[0], "Transitions.HomeOverlay");
+        }
     }
 
     private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index deebad5..d718e15 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -9,3 +9,6 @@
 chenghsiuchang@google.com
 atsjenk@google.com
 jorgegil@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 0652939..9fe2cb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -33,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -108,7 +112,7 @@
         MockitoAnnotations.initMocks(this);
         mExecutor = new TestShellExecutor();
         mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
                 mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
@@ -179,7 +183,7 @@
         // No diff
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
 
         verify(mWindowManager, never()).updateSurfacePosition();
@@ -200,7 +204,24 @@
         clearInvocations(mWindowManager);
         clearInvocations(mLayout);
         taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+        assertFalse(
+                mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+        verify(mWindowManager).release();
+
+        // Recreate button
+        clearInvocations(mWindowManager);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mWindowManager).release();
+        verify(mWindowManager).createLayout(/* canShow= */ true);
+
+        // Change has no launcher category and is not main intent, dispose the component
+        clearInvocations(mWindowManager);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_USB_STATE, "");
         assertFalse(
                 mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
         verify(mWindowManager).release();
@@ -217,7 +238,7 @@
         // inflated
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager, never()).inflateLayout();
@@ -225,7 +246,7 @@
         // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
         clearInvocations(mWindowManager);
         taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
@@ -304,7 +325,7 @@
         clearInvocations(mWindowManager);
         spyOn(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
 
         // User aspect ratio settings button has not yet been shown.
         doReturn(false).when(mUserAspectRatioButtonShownChecker).get();
@@ -378,7 +399,7 @@
     }
 
     private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
-            boolean topActivityBoundsLetterboxed) {
+            boolean topActivityBoundsLetterboxed, String action, String category) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.taskId = TASK_ID;
         taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
@@ -386,6 +407,7 @@
         taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
         taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+        taskInfo.baseIntent = new Intent(action).addCategory(category);
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 99cd4f3..855b7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -235,7 +235,7 @@
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
 
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
         verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
         verify(mStageCoordinator, never()).switchSplitPosition(any());
@@ -243,7 +243,6 @@
 
     @Test
     public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
-        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
         doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
@@ -260,8 +259,8 @@
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
-
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 50802c3..66efa02 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -40,12 +40,12 @@
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.TransitionMode;
-import android.window.WindowOrganizer;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
@@ -68,7 +68,7 @@
 @RunWith(AndroidJUnit4.class)
 public class HomeTransitionObserverTest extends ShellTestCase {
 
-    private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
+    private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
     private final TransactionPool mTransactionPool = mock(TransactionPool.class);
     private final Context mContext =
             InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 01c9bd0..e22bf3d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -87,7 +87,6 @@
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -98,6 +97,7 @@
 import com.android.internal.R;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
@@ -130,7 +130,7 @@
 @RunWith(AndroidJUnit4.class)
 public class ShellTransitionTests extends ShellTestCase {
 
-    private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
+    private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
     private final TransactionPool mTransactionPool = mock(TransactionPool.class);
     private final Context mContext =
             InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 79a7357..4741170 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -79,14 +79,6 @@
         "external/skia/src/core",
     ],
 
-    product_variables: {
-        eng: {
-            lto: {
-                never: true,
-            },
-        },
-    },
-
     target: {
         android: {
             include_dirs: [
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 65e1605..bba9c97 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -218,6 +218,15 @@
 
     mLocked.presentation = presentation;
 
+    if (input_flags::enable_pointer_choreographer()) {
+        // When pointer choreographer is enabled, the presentation mode is only set once when the
+        // PointerController is constructed, before the display viewport is provided.
+        // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
+        // is permanently enabled. The presentation can be set in the constructor.
+        mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+        return;
+    }
+
     if (!mCursorController.isViewportValid()) {
         return;
     }
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 8445032..69718a6 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -43,12 +43,15 @@
     },
     shared_libs: [
         "libandroid_runtime",
+        "libbase",
+        "libinput",
         "libinputservice",
         "libhwui",
         "libgui",
         "libutils",
     ],
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libgtest",
     ],
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index d9efd3c..adfa91e 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/PointerController.h>
@@ -28,6 +30,8 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 enum TestCursorType {
     CURSOR_TYPE_DEFAULT = 0,
     CURSOR_TYPE_HOVER,
@@ -261,7 +265,20 @@
     mPointerController->reloadPointerResources();
 }
 
-TEST_F(PointerControllerTest, updatePointerIcon) {
+TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+    // Setting the presentation mode before a display viewport is set will not load any resources.
+    mPointerController->setPresentation(PointerController::Presentation::POINTER);
+    ASSERT_TRUE(mPolicy->noResourcesAreLoaded());
+
+    // When the display is set, then the resources are loaded.
+    ensureDisplayViewportIsSet();
+    ASSERT_TRUE(mPolicy->allResourcesAreLoaded());
+}
+
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags,
+                                                       enable_pointer_choreographer))) {
     ensureDisplayViewportIsSet();
     mPointerController->setPresentation(PointerController::Presentation::POINTER);
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
@@ -277,6 +294,24 @@
     mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
 }
 
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+    // When PointerChoreographer is enabled, the presentation mode is set before the viewport.
+    mPointerController->setPresentation(PointerController::Presentation::POINTER);
+    ensureDisplayViewportIsSet();
+    mPointerController->unfade(PointerController::Transition::IMMEDIATE);
+
+    int32_t type = CURSOR_TYPE_ADDITIONAL;
+    std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
+    EXPECT_CALL(*mPointerSprite, setVisible(true));
+    EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+    EXPECT_CALL(*mPointerSprite,
+                setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
+                              Field(&SpriteIcon::hotSpotX, hotspot.first),
+                              Field(&SpriteIcon::hotSpotY, hotspot.second))));
+    mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
+}
+
 TEST_F(PointerControllerTest, setCustomPointerIcon) {
     ensureDisplayViewportIsSet();
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
diff --git a/media/OWNERS b/media/OWNERS
index 4a6648e..994a7b8 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -21,7 +21,6 @@
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 
 # SEO
-sungsoo@google.com
 
 # SEA/KIR/BVE
 jtinker@google.com
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index bbe5e06..058c5be 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -2,7 +2,6 @@
 fgoldfain@google.com
 elaurent@google.com
 lajos@google.com
-sungsoo@google.com
 jmtrivi@google.com
 
 # go/android-fwk-media-solutions for info on areas of ownership.
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 281eba6..6019aa8 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -156,7 +156,7 @@
     <string name="permission_storage">Photos and media</string>
 
     <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] -->
-    <string name="permission_notification">Notifications</string>
+    <string name="permission_notifications">Notifications</string>
 
     <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_app_streaming">Apps</string>
@@ -165,28 +165,31 @@
     <string name="permission_nearby_device_streaming">Streaming</string>
 
     <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_phone_summary">Can make and manage phone calls</string>
+    <string name="permission_phone_summary">Make and manage phone calls</string>
 
     <!-- Description of Call logs permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_call_logs_summary">Can read and write phone call log</string>
+    <string name="permission_call_logs_summary">Read and write phone call log</string>
 
     <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_sms_summary">Can send and view SMS messages</string>
+    <string name="permission_sms_summary">Send and view SMS messages</string>
 
     <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_contacts_summary">Can access your contacts</string>
+    <string name="permission_contacts_summary">Access your contacts</string>
 
     <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_calendar_summary">Can access your calendar</string>
+    <string name="permission_calendar_summary">Access your calendar</string>
 
     <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_microphone_summary">Can record audio</string>
+    <string name="permission_microphone_summary">Record audio</string>
 
     <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string>
+    <string name="permission_nearby_devices_summary">Find, connect to, and determine the relative position of nearby devices</string>
 
-    <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
+    <!-- Description of NLA (notification listener access) of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notification_listener_access_summary">Read all notifications, including information like contacts, messages, and photos</string>
+
+    <!-- Description of NLA & POST_NOTIFICATION of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notifications_summary">\u2022 Read all notifications, including info like contacts, messages, and photos&lt;br/>\u2022 Send notifications&lt;br/>&lt;br/>You can manage this app\'s ability to read and send notifications anytime in Settings > Notifications.</string>
 
     <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 97016f5..0abf285 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -27,13 +27,13 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
-import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
-import static com.android.companiondevicemanager.CompanionDeviceResources.TITLES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -482,7 +482,7 @@
             return;
         }
 
-        title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName);
+        title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
         setupPermissionList(deviceProfile);
 
         // Summary is not needed for selfManaged dialog.
@@ -525,7 +525,7 @@
 
         mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
 
-        final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
+        final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
 
         updatePermissionUi();
 
@@ -545,14 +545,14 @@
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
+        profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
 
         if (deviceProfile == null) {
             title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
             mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
         } else {
             title = getHtmlFromResources(this,
-                    R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile)));
+                    R.string.chooser_title, getString(PROFILE_NAMES.get(deviceProfile)));
         }
 
         mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked);
@@ -609,10 +609,10 @@
 
     private void updatePermissionUi() {
         final String deviceProfile = mRequest.getDeviceProfile();
-        final int summaryResourceId = SUMMARIES.get(deviceProfile);
+        final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
         final String remoteDeviceName = mSelectedDevice.getDisplayName();
         final Spanned title = getHtmlFromResources(
-                this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
+                this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
         final Spanned summary;
 
         // No need to show permission consent dialog if it is a isSkipPrompt(true)
@@ -680,7 +680,8 @@
     // and when mPermissionListRecyclerView is fully populated.
     // Lastly, disable the Allow and Don't allow buttons.
     private void setupPermissionList(String deviceProfile) {
-        final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile));
+        final List<Integer> permissionTypes = new ArrayList<>(
+                PROFILE_PERMISSIONS.get(deviceProfile));
         mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 551e975..23a11d6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -22,28 +22,15 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
-
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
 
+import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.media.flags.Flags;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +41,85 @@
  * for the corresponding profile.
  */
 final class CompanionDeviceResources {
-    static final Map<String, Integer> TITLES;
+
+    // Permission resources
+    private static final int PERMISSION_NOTIFICATION_LISTENER_ACCESS = 0;
+    private static final int PERMISSION_STORAGE = 1;
+    private static final int PERMISSION_APP_STREAMING = 2;
+    private static final int PERMISSION_PHONE = 3;
+    private static final int PERMISSION_SMS = 4;
+    private static final int PERMISSION_CONTACTS = 5;
+    private static final int PERMISSION_CALENDAR = 6;
+    private static final int PERMISSION_NEARBY_DEVICES = 7;
+    private static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
+    private static final int PERMISSION_MICROPHONE = 9;
+    private static final int PERMISSION_CALL_LOGS = 10;
+    // Notification Listener Access & POST_NOTIFICATION permission
+    private static final int PERMISSION_NOTIFICATIONS = 11;
+    private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 12;
+
+    static final Map<Integer, Integer> PERMISSION_TITLES;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.string.permission_notifications);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.string.permission_phone);
+        map.put(PERMISSION_SMS, R.string.permission_sms);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
+        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
+        map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
+        PERMISSION_TITLES = unmodifiableMap(map);
+    }
+
+    static final Map<Integer, Integer> PERMISSION_SUMMARIES;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                R.string.permission_notification_listener_access_summary);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
+        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
+        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.string.permission_nearby_device_streaming_summary);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
+        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
+        map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications_summary);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
+        PERMISSION_SUMMARIES = unmodifiableMap(map);
+    }
+
+    static final Map<Integer, Integer> PERMISSION_ICONS;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
+        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
+        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.drawable.ic_permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
+        map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
+        map.put(PERMISSION_NOTIFICATIONS, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
+        PERMISSION_ICONS = unmodifiableMap(map);
+    }
+
+    // Profile resources
+    static final Map<String, Integer> PROFILE_TITLES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
@@ -65,71 +130,61 @@
         map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
         map.put(null, R.string.confirmation_title);
 
-        TITLES = unmodifiableMap(map);
+        PROFILE_TITLES = unmodifiableMap(map);
     }
 
-    static final Map<String, List<Integer>> PERMISSION_TYPES;
-    static {
-        final Map<String, List<Integer>> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
-        map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
-                PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
-                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
-        if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
-            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
-                    PERMISSION_NEARBY_DEVICES));
-        } else {
-            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
-                    PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
-        }
-        map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
-                PERMISSION_NEARBY_DEVICES));
-
-        PERMISSION_TYPES = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> SUMMARIES;
+    static final Map<String, Integer> PROFILE_SUMMARIES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
         map.put(null, R.string.summary_generic);
 
-        SUMMARIES = unmodifiableMap(map);
+        PROFILE_SUMMARIES = unmodifiableMap(map);
     }
 
-    static final Map<String, Integer> PROFILES_NAME;
+    static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
+    static {
+        final Map<String, List<Integer>> map = new ArrayMap<>();
+        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+        map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
+                PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
+        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
+                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+                    PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
+        } else {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                    PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS,
+                    PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES));
+        }
+        map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
+                PERMISSION_NEARBY_DEVICES));
+
+        PROFILE_PERMISSIONS = unmodifiableMap(map);
+    }
+
+    static final Map<String, Integer> PROFILE_NAMES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_glasses);
         map.put(null, R.string.profile_name_generic);
 
-        PROFILES_NAME = unmodifiableMap(map);
+        PROFILE_NAMES = unmodifiableMap(map);
     }
 
-    static final Map<String, Integer> PROFILES_NAME_MULTI;
-    static {
-        final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic);
-        map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
-        map.put(null, R.string.profile_name_generic);
-
-        PROFILES_NAME_MULTI = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> PROFILE_ICON;
+    static final Map<String, Integer> PROFILE_ICONS;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.drawable.ic_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.drawable.ic_glasses);
         map.put(null, R.drawable.ic_device_other);
 
-        PROFILE_ICON = unmodifiableMap(map);
+        PROFILE_ICONS = unmodifiableMap(map);
     }
 
     static final Set<String> SUPPORTED_PROFILES;
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index e21aee3..4a1f801 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -16,14 +16,14 @@
 
 package com.android.companiondevicemanager;
 
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_ICONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_SUMMARIES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TITLES;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
 
-import static java.util.Collections.unmodifiableMap;
-
 import android.content.Context;
 import android.text.Spanned;
-import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,7 +35,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.List;
-import java.util.Map;
 
 class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> {
     private final Context mContext;
@@ -43,75 +42,6 @@
     // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list.
     private static final int PERMISSION_SIZE = 2;
 
-    static final int PERMISSION_NOTIFICATION = 0;
-    static final int PERMISSION_STORAGE = 1;
-    static final int PERMISSION_APP_STREAMING = 2;
-    static final int PERMISSION_PHONE = 3;
-    static final int PERMISSION_SMS = 4;
-    static final int PERMISSION_CONTACTS = 5;
-    static final int PERMISSION_CALENDAR = 6;
-    static final int PERMISSION_NEARBY_DEVICES = 7;
-    static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
-    static final int PERMISSION_MICROPHONE = 9;
-    static final int PERMISSION_CALL_LOGS = 10;
-    static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11;
-
-    private static final Map<Integer, Integer> sTitleMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification);
-        map.put(PERMISSION_STORAGE, R.string.permission_storage);
-        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
-        map.put(PERMISSION_PHONE, R.string.permission_phone);
-        map.put(PERMISSION_SMS, R.string.permission_sms);
-        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
-        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
-        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
-        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
-        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
-        sTitleMap = unmodifiableMap(map);
-    }
-
-    private static final Map<Integer, Integer> sSummaryMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary);
-        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
-        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
-        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
-        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
-        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
-        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
-        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
-                R.string.permission_nearby_device_streaming_summary);
-        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
-        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
-        sSummaryMap = unmodifiableMap(map);
-    }
-
-    private static final Map<Integer, Integer> sIconMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications);
-        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
-        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
-        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
-        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
-        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
-        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
-        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
-                R.drawable.ic_permission_nearby_device_streaming);
-        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
-        map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
-        sIconMap = unmodifiableMap(map);
-    }
-
     PermissionListAdapter(Context context) {
         mContext = context;
     }
@@ -121,7 +51,8 @@
         View view = LayoutInflater.from(parent.getContext()).inflate(
                 R.layout.list_item_permission, parent, false);
         ViewHolder viewHolder = new ViewHolder(view);
-        viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType)));
+        viewHolder.mPermissionIcon.setImageDrawable(
+                getIcon(mContext, PERMISSION_ICONS.get(viewType)));
 
         if (viewHolder.mExpandButton.getTag() == null) {
             viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
@@ -165,8 +96,8 @@
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
         int type = getItemViewType(position);
-        final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type));
-        final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type));
+        final Spanned title = getHtmlFromResources(mContext, PERMISSION_TITLES.get(type));
+        final Spanned summary = getHtmlFromResources(mContext, PERMISSION_SUMMARIES.get(type));
 
         holder.mPermissionSummary.setText(summary);
         holder.mPermissionName.setText(title);
@@ -192,6 +123,7 @@
         private final TextView mPermissionSummary;
         private final ImageView mPermissionIcon;
         private final ImageButton mExpandButton;
+
         ViewHolder(View itemView) {
             super(itemView);
             mPermissionName = itemView.findViewById(R.id.permission_name);
@@ -203,7 +135,7 @@
 
     private void setAccessibility(View view, int viewType, int action, int statusResourceId,
             int actionResourceId) {
-        final String permission = mContext.getString(sTitleMap.get(viewType));
+        final String permission = mContext.getString(PERMISSION_TITLES.get(viewType));
 
         if (actionResourceId != 0) {
             view.announceForAccessibility(
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 1c8a8d5..e3b93ba 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -85,6 +85,8 @@
 
     <!-- [CHAR LIMIT=15] -->
     <string name="ok">OK</string>
+    <!-- Confirmation text label for button to archive an application. Archiving means uninstalling the app without deleting user's personal data and replacing the app with a stub app with minimum size. So, the user can unarchive the app later and not lose any personal data. -->
+    <string name="archive">Archive</string>
     <!-- [CHAR LIMIT=30] -->
     <string name="update_anyway">Update anyway</string>
     <!-- [CHAR LIMIT=15] -->
@@ -115,6 +117,16 @@
     <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_application_text">Do you want to uninstall this app?</string>
     <!--  [CHAR LIMIT=none] -->
+    <string name="archive_application_text">Your personal data will be saved</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="archive_application_text_all_users">Archive this app for all users? Your personal data will be saved</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="archive_application_text_current_user_work_profile">Archive this app on your work profile? Your personal data will be saved</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="archive_application_text_user">Archive this app for <xliff:g id="username">%1$s</xliff:g>? Your personal data will be saved</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="archive_application_text_current_user_private_profile">Do you want to archive this app from your private space? Your personal data will be saved</string>
+    <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_application_text_all_users">Do you want to uninstall this app for <b>all</b>
         users?  The application and its data will be removed from <b>all</b> users on the device.</string>
     <!--  [CHAR LIMIT=none] -->
@@ -239,6 +251,8 @@
 
     <!-- Label for cloned app in uninstall dialogue [CHAR LIMIT=40] -->
     <string name="cloned_app_label"><xliff:g id="package_label">%1$s</xliff:g> Clone</string>
+    <!-- Label for archiving an app in uninstall dialogue -->
+    <string name="archiving_app_label">Archive <xliff:g id="package_label">%1$s</xliff:g>?</string>
 
     <!-- Label for button to continue install of an app whose source cannot be identified [CHAR LIMIT=40] -->
     <string name="anonymous_source_continue">Continue</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e889050..e07e942 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -130,6 +130,9 @@
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+        final boolean isArchive =
+                android.content.pm.Flags.archiving() && (
+                        (dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = getContext().getSystemService(UserManager.class);
         if (isUpdate) {
@@ -140,7 +143,9 @@
             }
         } else {
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
-                messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
+                messageBuilder.append(
+                        isArchive ? getString(R.string.archive_application_text_all_users)
+                                : getString(R.string.uninstall_application_text_all_users));
             } else if (!dialogInfo.user.equals(myUserHandle)) {
                 int userId = dialogInfo.user.getIdentifier();
                 UserManager customUserManager = getContext()
@@ -150,9 +155,11 @@
 
                 if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
-                    messageBuilder.append(
-                            getString(R.string.uninstall_application_text_current_user_work_profile,
-                                    userName));
+                    messageBuilder.append(isArchive
+                            ? getString(R.string.archive_application_text_current_user_work_profile,
+                                    userName) : getString(
+                            R.string.uninstall_application_text_current_user_work_profile,
+                            userName));
                 } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     mIsClonedApp = true;
@@ -161,9 +168,14 @@
                 } else if (Flags.allowPrivateProfile()
                         && customUserManager.isPrivateProfile()
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
-                    messageBuilder.append(getString(
+                    messageBuilder.append(isArchive ? getString(
+                            R.string.archive_application_text_current_user_private_profile,
+                            userName) : getString(
                             R.string.uninstall_application_text_current_user_private_profile,
                             userName));
+                } else if (isArchive) {
+                    messageBuilder.append(
+                            getString(R.string.archive_application_text_user, userName));
                 } else {
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_user, userName));
@@ -172,24 +184,27 @@
                 mIsClonedApp = true;
                 messageBuilder.append(getString(
                         R.string.uninstall_application_text_current_user_clone_profile));
+            } else if (Process.myUserHandle().equals(UserHandle.SYSTEM)
+                    && hasClonedInstance(dialogInfo.appInfo.packageName)) {
+                messageBuilder.append(getString(
+                        R.string.uninstall_application_text_with_clone_instance,
+                        appLabel));
+            } else if (isArchive) {
+                messageBuilder.append(getString(R.string.archive_application_text));
             } else {
-                if (Process.myUserHandle().equals(UserHandle.SYSTEM)
-                        && hasClonedInstance(dialogInfo.appInfo.packageName)) {
-                    messageBuilder.append(getString(
-                            R.string.uninstall_application_text_with_clone_instance,
-                            appLabel));
-                } else {
-                    messageBuilder.append(getString(R.string.uninstall_application_text));
-                }
+                messageBuilder.append(getString(R.string.uninstall_application_text));
             }
         }
 
         if (mIsClonedApp) {
             dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel));
+        } else if (isArchive) {
+            dialogBuilder.setTitle(getString(R.string.archiving_app_label, appLabel));
         } else {
             dialogBuilder.setTitle(appLabel);
         }
-        dialogBuilder.setPositiveButton(android.R.string.ok, this);
+        dialogBuilder.setPositiveButton(isArchive ? R.string.archive : android.R.string.ok,
+                this);
         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
 
         String pkg = dialogInfo.appInfo.packageName;
@@ -199,7 +214,7 @@
             PackageInfo pkgInfo = pm.getPackageInfo(pkg,
                     PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES));
 
-            suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
+            suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData() && !isArchive;
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e);
             suggestToKeepAppData = false;
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 96a11ee..5b39f4e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -112,26 +112,6 @@
     }
 
     /**
-     * Shows restricted setting dialog.
-     */
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    public static void sendShowRestrictedSettingDialogIntent(Context context,
-            String packageName, int uid) {
-        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
-        context.startActivity(intent);
-    }
-
-    /**
-     * Gets restricted settings dialog intent.
-     */
-    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
-        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        return intent;
-    }
-
-    /**
      * Checks if current user is profile or not
      */
     @RequiresApi(Build.VERSION_CODES.M)
@@ -238,4 +218,35 @@
                     + '}';
         }
     }
+
+
+    /**
+     * Shows restricted setting dialog.
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public static void sendShowRestrictedSettingDialogIntent(Context context,
+                                                             String packageName, int uid) {
+        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+        context.startActivity(intent);
+    }
+
+    /**
+     * Gets restricted settings dialog intent.
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        return intent;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 4454b71..0237446 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -77,6 +77,9 @@
             ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
             ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
         }
+
+        ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+        ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index db2a6ec..50e3bd0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -96,12 +96,29 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         if (enabled && isDisabledByAdmin()) {
             mHelper.setDisabledByAdmin(null);
             return;
         }
+
+        if (enabled && isDisabledByEcm()) {
+            mHelper.setDisabledByEcm(null);
+            return;
+        }
+
         super.setEnabled(enabled);
     }
 
@@ -111,16 +128,14 @@
         }
     }
 
-    public void setDisabledByAppOps(boolean disabled) {
-        if (mHelper.setDisabledByAppOps(disabled)) {
-            notifyChanged();
-        }
-    }
-
     public boolean isDisabledByAdmin() {
         return mHelper.isDisabledByAdmin();
     }
 
+    public boolean isDisabledByEcm() {
+        return mHelper.isDisabledByEcm();
+    }
+
     public int getUid() {
         return mHelper != null ? mHelper.uid : Process.INVALID_UID;
     }
@@ -128,4 +143,16 @@
     public String getPackageName() {
         return mHelper != null ? mHelper.packageName : null;
     }
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    public void setDisabledByAppOps(boolean disabled) {
+        if (mHelper.setDisabledByAppOps(disabled)) {
+            notifyChanged();
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 29ea25e..a479269 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -17,10 +17,12 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.UserHandle;
@@ -52,7 +54,8 @@
     private String mAttrUserRestriction = null;
     private boolean mDisabledSummary = false;
 
-    private boolean mDisabledByAppOps;
+    private boolean mDisabledByEcm;
+    private Intent mDisabledByEcmIntent = null;
 
     public RestrictedPreferenceHelper(Context context, Preference preference,
             AttributeSet attrs, String packageName, int uid) {
@@ -101,7 +104,7 @@
      * Modify PreferenceViewHolder to add padlock if restriction is disabled.
      */
     public void onBindViewHolder(PreferenceViewHolder holder) {
-        if (mDisabledByAdmin || mDisabledByAppOps) {
+        if (mDisabledByAdmin || mDisabledByEcm) {
             holder.itemView.setEnabled(true);
         }
         if (mDisabledSummary) {
@@ -112,7 +115,7 @@
                         : mContext.getString(R.string.disabled_by_admin_summary_text);
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
-                } else if (mDisabledByAppOps) {
+                } else if (mDisabledByEcm) {
                     summaryView.setText(R.string.disabled_by_app_ops_text);
                 } else if (TextUtils.equals(disabledText, summaryView.getText())) {
                     // It's previously set to disabled text, clear it.
@@ -144,7 +147,12 @@
             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
             return true;
         }
-        if (mDisabledByAppOps) {
+        if (mDisabledByEcm) {
+            if (android.security.Flags.extendEcmToAllSettings()) {
+                mContext.startActivity(mDisabledByEcmIntent);
+                return true;
+            }
+
             RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
                     uid);
             return true;
@@ -174,6 +182,20 @@
     }
 
     /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        updatePackageDetails(packageName, uid);
+        Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation(
+                mContext, restriction, uid, packageName);
+        setDisabledByEcm(intent);
+    }
+
+    /**
      * @return EnforcedAdmin if we have been passed the restriction in the xml.
      */
     public EnforcedAdmin checkRestrictionEnforced() {
@@ -211,10 +233,19 @@
         return changed;
     }
 
-    public boolean setDisabledByAppOps(boolean disabled) {
+    /**
+     * Disable the preference based on the passed in Intent
+     * @param disabledIntent The intent which is started when the user clicks the disabled
+     * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will
+     * be disabled.
+     * @return true if the disabled state was changed.
+     */
+    public boolean setDisabledByEcm(Intent disabledIntent) {
+        boolean disabled = disabledIntent != null;
         boolean changed = false;
-        if (mDisabledByAppOps != disabled) {
-            mDisabledByAppOps = disabled;
+        if (mDisabledByEcm != disabled) {
+            mDisabledByEcmIntent = disabledIntent;
+            mDisabledByEcm = disabled;
             changed = true;
             updateDisabledState();
         }
@@ -226,8 +257,8 @@
         return mDisabledByAdmin;
     }
 
-    public boolean isDisabledByAppOps() {
-        return mDisabledByAppOps;
+    public boolean isDisabledByEcm() {
+        return mDisabledByEcm;
     }
 
     public void updatePackageDetails(String packageName, int uid) {
@@ -236,13 +267,31 @@
     }
 
     private void updateDisabledState() {
+        boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm);
         if (!(mPreference instanceof RestrictedTopLevelPreference)) {
-            mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+            mPreference.setEnabled(isEnabled);
         }
 
         if (mPreference instanceof PrimarySwitchPreference) {
-            ((PrimarySwitchPreference) mPreference)
-                    .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+            ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
         }
     }
+
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    public boolean setDisabledByAppOps(boolean disabled) {
+        boolean changed = false;
+        if (mDisabledByEcm != disabled) {
+            mDisabledByEcm = disabled;
+            changed = true;
+            updateDisabledState();
+        }
+
+        return changed;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 60321eb..3b8f665 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -197,6 +197,17 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         boolean changed = false;
@@ -204,8 +215,8 @@
             mHelper.setDisabledByAdmin(null);
             changed = true;
         }
-        if (enabled && isDisabledByAppOps()) {
-            mHelper.setDisabledByAppOps(false);
+        if (enabled && isDisabledByEcm()) {
+            mHelper.setDisabledByEcm(null);
             changed = true;
         }
         if (!changed) {
@@ -223,25 +234,50 @@
         return mHelper.isDisabledByAdmin();
     }
 
+    public boolean isDisabledByEcm() {
+        return mHelper.isDisabledByEcm();
+    }
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     private void setDisabledByAppOps(boolean disabled) {
         if (mHelper.setDisabledByAppOps(disabled)) {
             notifyChanged();
         }
     }
 
-    public boolean isDisabledByAppOps() {
-        return mHelper.isDisabledByAppOps();
-    }
-
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public int getUid() {
         return mHelper != null ? mHelper.uid : Process.INVALID_UID;
     }
 
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public String getPackageName() {
         return mHelper != null ? mHelper.packageName : null;
     }
 
-    /** Updates enabled state based on associated package. */
+    /**
+     * Updates enabled state based on associated package
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public void updateState(
             @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) {
         mHelper.updatePackageDetails(packageName, uid);
@@ -258,7 +294,7 @@
             setEnabled(false);
         } else if (isEnabled) {
             setEnabled(true);
-        } else if (appOpsAllowed && isDisabledByAppOps()) {
+        } else if (appOpsAllowed && isDisabledByEcm()) {
             setEnabled(true);
         } else if (!appOpsAllowed){
             setDisabledByAppOps(true);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5c09b16..ec456e0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -73,6 +73,7 @@
         Settings.Secure.TTS_ENABLED_PLUGINS,
         Settings.Secure.TTS_DEFAULT_LOCALE,
         Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+        Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS,
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,            // moved to global
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,               // moved to global
         Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT,                        // moved to global
@@ -105,6 +106,9 @@
         Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
         Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
         Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
+        // ACCESSIBILITY_QS_TARGETS needs to be restored after ENABLED_ACCESSIBILITY_SERVICES
+        // but before QS_TILES
+        Settings.Secure.ACCESSIBILITY_QS_TARGETS,
         Settings.Secure.QS_TILES,
         Settings.Secure.QS_AUTO_ADDED_TILES,
         Settings.Secure.CONTROLS_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b0169a1..5ad14ce 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -119,6 +119,7 @@
         VALIDATORS.put(Secure.TTS_ENABLED_PLUGINS, new PackageNameListValidator(" "));
         VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -319,6 +320,9 @@
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_BUTTON_TARGETS,
                 ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_QS_TARGETS,
+                ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a978889..5afcd5d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1813,6 +1813,9 @@
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                 SecureSettingsProto.Accessibility.BUTTON_TARGETS);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+                SecureSettingsProto.Accessibility.QS_TARGETS);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
                 SecureSettingsProto.Accessibility.ACCESSIBILITY_MAGNIFICATION_CAPABILITY);
         dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d12d9d6..bacab0f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -879,6 +879,9 @@
 
     <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" />
 
+    <!-- Permissions required for CTS test - CtsAccessibilityServiceTestCases-->
+    <uses-permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7061e2c..f10ac1b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -204,6 +204,7 @@
         "lottie",
         "LowLightDreamLib",
         "motion_tool_lib",
+        "notification_flags_lib",
     ],
     libs: [
         "keepanno-annotations",
@@ -328,6 +329,7 @@
         "androidx.compose.ui_ui",
         "flag-junit",
         "platform-test-annotations",
+        "notification_flags_lib",
     ],
 }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index f7d9056c3..9c46ebdc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -21,7 +21,6 @@
 import android.app.PendingIntent
 import android.app.TaskInfo
 import android.graphics.Matrix
-import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
 import android.os.Build
@@ -37,7 +36,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
-import android.view.animation.Interpolator
 import android.view.animation.PathInterpolator
 import androidx.annotation.AnyThread
 import androidx.annotation.BinderThread
@@ -93,7 +91,7 @@
         val INTERPOLATORS =
             LaunchAnimator.Interpolators(
                 positionInterpolator = Interpolators.EMPHASIZED,
-                positionXInterpolator = createPositionXInterpolator(),
+                positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT,
                 contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
                 contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
             )
@@ -121,16 +119,6 @@
          * cancelled by WM.
          */
         private const val LONG_LAUNCH_TIMEOUT = 5_000L
-
-        private fun createPositionXInterpolator(): Interpolator {
-            val path =
-                Path().apply {
-                    moveTo(0f, 0f)
-                    cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
-                    cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
-                }
-            return PathInterpolator(path)
-        }
     }
 
     /**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 87c2886..1d7c7d63 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package com.android.systemui.bouncer.ui.composable
 
 import android.app.AlertDialog
@@ -29,18 +31,17 @@
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.KeyboardArrowDown
@@ -51,6 +52,7 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -78,6 +80,7 @@
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -100,37 +103,41 @@
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
-    val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
     val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
     val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
 
-    when (layout) {
-        BouncerSceneLayout.STANDARD ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SIDE_BY_SIDE ->
-            SideBySideLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.STACKED ->
-            StackedLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SPLIT ->
-            SplitLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
+    Box(
+        // Allows the content within each of the layouts to react to the appearance and
+        // disappearance of the IME, which is also known as the software keyboard.
+        //
+        // Despite the keyboard only being part of the password bouncer, adding it at this level is
+        // both necessary to properly handle the keyboard in all layouts and harmless in cases when
+        // the keyboard isn't used (like the PIN or pattern auth methods).
+        modifier = modifier.imePadding(),
+    ) {
+        when (layout) {
+            BouncerSceneLayout.STANDARD_BOUNCER ->
+                StandardLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BESIDE_USER_SWITCHER ->
+                BesideUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BELOW_USER_SWITCHER ->
+                BelowUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.SPLIT_BOUNCER ->
+                SplitLayout(
+                    viewModel = viewModel,
+                )
+        }
+
+        Dialog(
+            viewModel = viewModel,
+            dialogFactory = dialogFactory,
+        )
     }
 }
 
@@ -141,15 +148,330 @@
 @Composable
 private fun StandardLayout(
     viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
-    layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD,
-    outputOnly: Boolean = false,
+) {
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+
+    FoldAware(
+        modifier =
+            modifier.padding(
+                top = 92.dp,
+                bottom = 48.dp,
+            ),
+        viewModel = viewModel,
+        aboveFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                    modifier = Modifier,
+                )
+
+                OutputArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp),
+                )
+            }
+        },
+        belowFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                Box(
+                    modifier = Modifier.weight(1f),
+                ) {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 12.dp,
+                        centerPatternDotsVertically = false,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        },
+    )
+}
+
+/**
+ * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
+ * by double-tapping on the side.
+ */
+@Composable
+private fun SplitLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier
+                .fillMaxHeight()
+                .padding(
+                    horizontal = 24.dp,
+                    vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp,
+                ),
+    ) {
+        // Left side (in left-to-right locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center))
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp),
+                    )
+                }
+                is PatternBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+                else -> Unit
+            }
+        }
+
+        // Right side (in right-to-left locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel,
+                is PatternBouncerViewModel -> {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 8.dp,
+                        centerPatternDotsVertically = true,
+                        modifier = Modifier.align(Alignment.Center),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    Column(
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                        modifier = Modifier.fillMaxWidth().align(Alignment.Center),
+                    ) {
+                        StatusMessage(
+                            viewModel = viewModel,
+                        )
+
+                        OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ */
+@Composable
+private fun BesideUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier
+                .pointerInput(Unit) {
+                    detectTapGestures(
+                        onDoubleTap = { offset ->
+                            // Depending on where the user double tapped, switch the elements such
+                            // that the non-swapped element is closer to the side that was double
+                            // tapped.
+                            setSwapped(offset.x < size.width / 2)
+                        }
+                    )
+                }
+                .padding(
+                    top = if (isHeightExpanded) 128.dp else 96.dp,
+                    bottom = if (isHeightExpanded) 128.dp else 48.dp,
+                ),
+    ) {
+        val animatedOffset by
+            animateFloatAsState(
+                targetValue =
+                    if (!isSwapped) {
+                        // A non-swapped element has its natural placement so it's not offset.
+                        0f
+                    } else if (isLeftToRight) {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // LTR locales, this means pushing the element to the right, hence the
+                        // positive number.
+                        1f
+                    } else {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // RTL locales, this means pushing the element to the left, hence the
+                        // negative number.
+                        -1f
+                    },
+                label = "offset",
+            )
+
+        fun Modifier.swappable(inversed: Boolean = false): Modifier {
+            return graphicsLayer {
+                translationX =
+                    size.width *
+                        animatedOffset *
+                        if (inversed) {
+                            // A negative sign is used to make sure this is offset in the direction
+                            // that's opposite to the direction that the user switcher is pushed in.
+                            -1
+                        } else {
+                            1
+                        }
+                alpha = animatedAlpha(animatedOffset)
+            }
+        }
+
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.weight(1f).swappable(),
+        )
+
+        FoldAware(
+            modifier = Modifier.weight(1f).swappable(inversed = true),
+            viewModel = viewModel,
+            aboveFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth()
+                ) {
+                    StatusMessage(
+                        viewModel = viewModel,
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                }
+            },
+            belowFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    val isOutputAreaVisible = authMethod !is PatternBouncerViewModel
+                    // If there is an output area and the window is not tall enough, spacing needs
+                    // to be added between the input and the output areas (otherwise the two get
+                    // very squished together).
+                    val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded
+
+                    Box(
+                        modifier =
+                            Modifier.weight(1f)
+                                .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp),
+                    ) {
+                        InputArea(
+                            viewModel = viewModel,
+                            pinButtonRowVerticalSpacing = 12.dp,
+                            centerPatternDotsVertically = true,
+                            modifier = Modifier.align(Alignment.BottomCenter),
+                        )
+                    }
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.padding(top = 48.dp),
+                    )
+                }
+            },
+        )
+    }
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
+@Composable
+private fun BelowUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier =
+            modifier.padding(
+                vertical = 128.dp,
+            )
+    ) {
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.fillMaxWidth(),
+        )
+
+        Spacer(Modifier.weight(1f))
+
+        Box(modifier = Modifier.fillMaxWidth()) {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                )
+
+                OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+
+                InputArea(
+                    viewModel = viewModel,
+                    pinButtonRowVerticalSpacing = 12.dp,
+                    centerPatternDotsVertically = true,
+                    modifier = Modifier.padding(top = 128.dp),
+                )
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun FoldAware(
+    viewModel: BouncerViewModel,
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     val foldPosture: FoldPosture by foldPosture()
     val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
-    val isSplitAroundTheFold =
-        foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+    val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
@@ -160,115 +482,57 @@
         modifier = modifier,
     ) {
         scene(SceneKeys.ContiguousSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = false,
             )
         }
 
         scene(SceneKeys.SplitSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = true,
             )
         }
     }
 }
 
-/**
- * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
- * switcher UI) and laid out vertically, centered horizontally.
- *
- * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
- * render across the location of the fold hardware when the device is fully or part-way unfolded
- * with the fold hinge in a horizontal position.
- *
- * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
- * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
- * their PIN or pattern.
- */
 @Composable
-private fun SceneScope.FoldSplittable(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    layout: BouncerSceneLayout,
-    outputOnly: Boolean,
+private fun SceneScope.FoldableScene(
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
     isSplit: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val dialogMessage: String? by viewModel.dialogMessage.collectAsState()
-    var dialog: Dialog? by remember { mutableStateOf(null) }
-    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
     val splitRatio =
         LocalContext.current.resources.getFloat(
             R.dimen.motion_layout_half_fold_bouncer_height_ratio
         )
 
-    Column(modifier = modifier.padding(horizontal = 32.dp)) {
+    Column(
+        modifier = modifier.fillMaxHeight(),
+    ) {
         // Content above the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.AboveFold)
-                    .fillMaxWidth()
                     .then(
                         if (isSplit) {
                             Modifier.weight(splitRatio)
-                        } else if (outputOnly) {
-                            Modifier.fillMaxHeight()
                         } else {
                             Modifier
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding),
-            ) {
-                Crossfade(
-                    targetState = message,
-                    label = "Bouncer message",
-                    animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-                ) { message ->
-                    Text(
-                        text = message.text,
-                        color = MaterialTheme.colorScheme.onSurface,
-                        style = MaterialTheme.typography.bodyLarge,
-                    )
-                }
-
-                if (!outputOnly) {
-                    Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput))
-
-                    UserInputArea(
-                        viewModel = viewModel,
-                        visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                        layout = layout,
-                    )
-                }
-            }
-
-            if (outputOnly) {
-                UserInputArea(
-                    viewModel = viewModel,
-                    visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                    layout = layout,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
+            aboveFold()
         }
 
         // Content below the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.BelowFold)
-                    .fillMaxWidth()
                     .weight(
                         if (isSplit) {
                             1 - splitRatio
@@ -277,73 +541,40 @@
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                if (!outputOnly) {
-                    Box(Modifier.weight(1f)) {
-                        UserInputArea(
-                            viewModel = viewModel,
-                            visibility = UserInputAreaVisibility.INPUT_ONLY,
-                            layout = layout,
-                            modifier = Modifier.align(Alignment.BottomCenter),
-                        )
-                    }
-                }
-
-                Spacer(Modifier.height(48.dp))
-
-                val actionButtonModifier = Modifier.height(56.dp)
-
-                actionButton.let { actionButtonViewModel ->
-                    if (actionButtonViewModel != null) {
-                        BouncerActionButton(
-                            viewModel = actionButtonViewModel,
-                            modifier = actionButtonModifier,
-                        )
-                    } else {
-                        Spacer(modifier = actionButtonModifier)
-                    }
-                }
-
-                Spacer(Modifier.height(layout.bottomPadding))
-            }
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
-            }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+            belowFold()
         }
     }
 }
 
+@Composable
+private fun StatusMessage(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+
+    Crossfade(
+        targetState = message,
+        label = "Bouncer message",
+        animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+        modifier = modifier,
+    ) {
+        Text(
+            text = it.text,
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.bodyLarge,
+        )
+    }
+}
+
 /**
- * Renders the user input area, where the user interacts with the UI to enter their credentials.
+ * Renders the user output area, where the user sees what they entered.
  *
- * For example, this can be the pattern input area, the password text box, or pin pad.
+ * For example, this can be the PIN shapes or password text field.
  */
 @Composable
-private fun UserInputArea(
+private fun OutputArea(
     viewModel: BouncerViewModel,
-    visibility: UserInputAreaVisibility,
-    layout: BouncerSceneLayout,
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -351,66 +582,115 @@
 
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel ->
-            when (visibility) {
-                UserInputAreaVisibility.OUTPUT_ONLY ->
-                    PinInputDisplay(
-                        viewModel = nonNullViewModel,
-                        modifier = modifier,
-                    )
-                UserInputAreaVisibility.INPUT_ONLY ->
-                    PinPad(
-                        viewModel = nonNullViewModel,
-                        layout = layout,
-                        modifier = modifier,
-                    )
-            }
+            PinInputDisplay(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         is PasswordBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PasswordBouncer(
-                    viewModel = nonNullViewModel,
-                    modifier = modifier,
-                )
-            }
-        is PatternBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PatternBouncer(
-                    viewModel = nonNullViewModel,
-                    layout = layout,
-                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false),
-                )
-            }
+            PasswordBouncer(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         else -> Unit
     }
 }
 
 /**
- * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ * Renders the user input area, where the user enters their credentials.
+ *
+ * For example, this can be the pattern input area or the PIN pad.
  */
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun BouncerActionButton(
-    viewModel: BouncerActionButtonModel,
+private fun InputArea(
+    viewModel: BouncerViewModel,
+    pinButtonRowVerticalSpacing: Dp,
+    centerPatternDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Button(
-        onClick = viewModel.onClick,
-        modifier =
-            modifier.thenIf(viewModel.onLongClick != null) {
-                Modifier.combinedClickable(
-                    onClick = viewModel.onClick,
-                    onLongClick = viewModel.onLongClick,
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
+
+    when (val nonNullViewModel = authMethodViewModel) {
+        is PinBouncerViewModel -> {
+            PinPad(
+                viewModel = nonNullViewModel,
+                verticalSpacing = pinButtonRowVerticalSpacing,
+                modifier = modifier,
+            )
+        }
+        is PatternBouncerViewModel -> {
+            PatternBouncer(
+                viewModel = nonNullViewModel,
+                centerDotsVertically = centerPatternDotsVertically,
+                modifier = modifier,
+            )
+        }
+        else -> Unit
+    }
+}
+
+@Composable
+private fun ActionArea(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+
+    actionButton?.let { actionButtonViewModel ->
+        Box(
+            modifier = modifier,
+        ) {
+            Button(
+                onClick = actionButtonViewModel.onClick,
+                modifier =
+                    Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) {
+                        Modifier.combinedClickable(
+                            onClick = actionButtonViewModel.onClick,
+                            onLongClick = actionButtonViewModel.onLongClick,
+                        )
+                    },
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = actionButtonViewModel.label,
+                    style = MaterialTheme.typography.bodyMedium,
                 )
-            },
-        colors =
-            ButtonDefaults.buttonColors(
-                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-            ),
-    ) {
-        Text(
-            text = viewModel.label,
-            style = MaterialTheme.typography.bodyMedium,
-        )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Dialog(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+) {
+    val dialogMessage: String? by viewModel.dialogMessage.collectAsState()
+    var dialog: Dialog? by remember { mutableStateOf(null) }
+
+    if (dialogMessage != null) {
+        if (dialog == null) {
+            dialog =
+                dialogFactory().apply {
+                    setMessage(dialogMessage)
+                    setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        context.getString(R.string.ok),
+                    ) { _, _ ->
+                        viewModel.onDialogDismissed()
+                    }
+                    setCancelable(false)
+                    setCanceledOnTouchOutside(false)
+                    show()
+                }
+        }
+    } else {
+        dialog?.dismiss()
+        dialog = null
     }
 }
 
@@ -420,6 +700,14 @@
     viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
+    if (!viewModel.isUserSwitcherVisible) {
+        // Take up the same space as the user switcher normally would, but with nothing inside it.
+        Box(
+            modifier = modifier,
+        )
+        return
+    }
+
     val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
     val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
 
@@ -539,195 +827,10 @@
     }
 }
 
-/**
- * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
- * by double-tapping on the side.
- */
-@Composable
-private fun SplitLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SPLIT,
-                outputOnly = true,
-                modifier = startContentModifier,
-            )
-        },
-        endContent = { endContentModifier ->
-            UserInputArea(
-                viewModel = viewModel,
-                visibility = UserInputAreaVisibility.INPUT_ONLY,
-                layout = BouncerSceneLayout.SPLIT,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SPLIT,
-        modifier = modifier,
-    )
-}
-
-/**
- * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
- * to flip their positions.
- */
-@Composable
-private fun SwappableLayout(
-    startContent: @Composable (Modifier) -> Unit,
-    endContent: @Composable (Modifier) -> Unit,
-    layout: BouncerSceneLayout,
-    modifier: Modifier = Modifier,
-) {
-    val layoutDirection = LocalLayoutDirection.current
-    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
-    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
-
-    Row(
-        modifier =
-            modifier.pointerInput(Unit) {
-                detectTapGestures(
-                    onDoubleTap = { offset ->
-                        // Depending on where the user double tapped, switch the elements such that
-                        // the endContent is closer to the side that was double tapped.
-                        setSwapped(offset.x < size.width / 2)
-                    }
-                )
-            },
-    ) {
-        val animatedOffset by
-            animateFloatAsState(
-                targetValue =
-                    if (!isSwapped) {
-                        // When startContent is first, both elements have their natural placement so
-                        // they are not offset in any way.
-                        0f
-                    } else if (isLeftToRight) {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of LTR locales, this means pushing startContent
-                        // to the right, hence the positive number.
-                        1f
-                    } else {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of RTL locales, this means pushing startContent
-                        // to the left, hence the negative number.
-                        -1f
-                    },
-                label = "offset",
-            )
-
-        startContent(
-            Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                translationX = size.width * animatedOffset
-                alpha = animatedAlpha(animatedOffset)
-            }
-        )
-
-        Box(
-            modifier =
-                Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                    // A negative sign is used to make sure this is offset in the direction that's
-                    // opposite of the direction that the user switcher is pushed in.
-                    translationX = -size.width * animatedOffset
-                    alpha = animatedAlpha(animatedOffset)
-                }
-        ) {
-            endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp))
-        }
-    }
-}
-
-/**
- * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
- * anywhere on the background to flip their positions.
- *
- * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
- * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
- * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
- * rendering of the bouncer will be used instead of the side-by-side layout.
- */
-@Composable
-private fun SideBySideLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            if (isUserSwitcherVisible) {
-                UserSwitcher(
-                    viewModel = viewModel,
-                    modifier = startContentModifier,
-                )
-            } else {
-                Box(
-                    modifier = startContentModifier,
-                )
-            }
-        },
-        endContent = { endContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SIDE_BY_SIDE,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SIDE_BY_SIDE,
-        modifier = modifier,
-    )
-}
-
-/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
-@Composable
-private fun StackedLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        modifier = modifier,
-    ) {
-        if (isUserSwitcherVisible) {
-            UserSwitcher(
-                viewModel = viewModel,
-                modifier = Modifier.fillMaxWidth().weight(1f),
-            )
-        }
-
-        StandardLayout(
-            viewModel = viewModel,
-            dialogFactory = dialogFactory,
-            layout = BouncerSceneLayout.STACKED,
-            modifier = Modifier.fillMaxWidth().weight(1f),
-        )
-    }
-}
-
 interface BouncerDialogFactory {
     operator fun invoke(): AlertDialog
 }
 
-/** Enumerates all supported user-input area visibilities. */
-private enum class UserInputAreaVisibility {
-    /**
-     * Only the area where the user enters the input is shown; the area where the input is reflected
-     * back to the user is not shown.
-     */
-    INPUT_ONLY,
-    /**
-     * Only the area where the input is reflected back to the user is shown; the area where the
-     * input is entered by the user is not shown.
-     */
-    OUTPUT_ONLY,
-}
-
 /**
  * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
  * the two reaches a stopping point but `0` in the middle of the transition.
@@ -774,48 +877,3 @@
 private val SceneTransitions = transitions {
     from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
 }
-
-/** Whether a more compact size should be used for various spacing dimensions. */
-internal val BouncerSceneLayout.isUseCompactSize: Boolean
-    get() =
-        when (this) {
-            BouncerSceneLayout.SIDE_BY_SIDE -> true
-            BouncerSceneLayout.SPLIT -> true
-            else -> false
-        }
-
-/** Amount of space to place between the message and the entered input UI elements, in dips. */
-private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp
-    get() =
-        when {
-            this == BouncerSceneLayout.STACKED -> 24.dp
-            isUseCompactSize -> 96.dp
-            else -> 128.dp
-        }
-
-/** Amount of space to place above the topmost UI element, in dips. */
-private val BouncerSceneLayout.topPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            92.dp
-        }
-
-/** Amount of space to place below the bottommost UI element, in dips. */
-private val BouncerSceneLayout.bottomPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            48.dp
-        }
-
-/** The in-a-box alignment for the content on the "end" side of a swappable layout. */
-private val BouncerSceneLayout.swappableEndContentAlignment: Alignment
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            Alignment.Center
-        } else {
-            Alignment.BottomCenter
-        }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
index 08b7559..1c3d93c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -26,8 +26,8 @@
 
 /**
  * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
- * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
- * [BouncerSceneLayout.STANDARD].
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by
+ * [BouncerSceneLayout.STANDARD_BOUNCER].
  */
 @Composable
 fun calculateLayout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2799959..0960811 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.ViewTreeObserver
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.LocalTextStyle
@@ -31,7 +30,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusRequester
@@ -81,42 +79,38 @@
         }
     }
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier,
-    ) {
-        val color = MaterialTheme.colorScheme.onSurfaceVariant
-        val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
+    val color = MaterialTheme.colorScheme.onSurfaceVariant
+    val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
 
-        TextField(
-            value = password,
-            onValueChange = viewModel::onPasswordInputChanged,
-            enabled = isInputEnabled,
-            visualTransformation = PasswordVisualTransformation(),
-            singleLine = true,
-            textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
-            keyboardOptions =
-                KeyboardOptions(
-                    keyboardType = KeyboardType.Password,
-                    imeAction = ImeAction.Done,
-                ),
-            keyboardActions =
-                KeyboardActions(
-                    onDone = { viewModel.onAuthenticateKeyPressed() },
-                ),
-            modifier =
-                Modifier.focusRequester(focusRequester)
-                    .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
-                    .drawBehind {
-                        drawLine(
-                            color = color,
-                            start = Offset(x = 0f, y = size.height - lineWidthPx),
-                            end = Offset(size.width, y = size.height - lineWidthPx),
-                            strokeWidth = lineWidthPx,
-                        )
-                    },
-        )
-    }
+    TextField(
+        value = password,
+        onValueChange = viewModel::onPasswordInputChanged,
+        enabled = isInputEnabled,
+        visualTransformation = PasswordVisualTransformation(),
+        singleLine = true,
+        textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+        keyboardOptions =
+            KeyboardOptions(
+                keyboardType = KeyboardType.Password,
+                imeAction = ImeAction.Done,
+            ),
+        keyboardActions =
+            KeyboardActions(
+                onDone = { viewModel.onAuthenticateKeyPressed() },
+            ),
+        modifier =
+            modifier
+                .focusRequester(focusRequester)
+                .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+                .drawBehind {
+                    drawLine(
+                        color = color,
+                        start = Offset(x = 0f, y = size.height - lineWidthPx),
+                        end = Offset(size.width, y = size.height - lineWidthPx),
+                        strokeWidth = lineWidthPx,
+                    )
+                },
+    )
 }
 
 /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index a4b1955..0a5f5d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -48,7 +50,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.modifiers.thenIf
 import com.android.internal.R
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
 import kotlin.math.min
@@ -61,11 +62,14 @@
  * UI for the input part of a pattern-requiring version of the bouncer.
  *
  * The user can press, hold, and drag their pointer to select dots along a grid of dots.
+ *
+ * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if
+ * `false`, the dots will be pushed towards the end/bottom of the axis.
  */
 @Composable
 internal fun PatternBouncer(
     viewModel: PatternBouncerViewModel,
-    layout: BouncerSceneLayout,
+    centerDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -197,6 +201,14 @@
 
     Canvas(
         modifier
+            // Because the width also includes spacing to the left and right of the leftmost and
+            // rightmost dots in the grid and because UX mocks specify the width without that
+            // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
+            .width((262 * colCount / 2).dp)
+            // Because the height also includes spacing above and below the topmost and bottommost
+            // dots in the grid and because UX mocks specify the height without that spacing, the
+            // actual height needs to be defined slightly bigger than the UX mock height.
+            .height((262 * rowCount / 2).dp)
             // Need to clip to bounds to make sure that the lines don't follow the input pointer
             // when it leaves the bounds of the dot grid.
             .clipToBounds()
@@ -260,7 +272,7 @@
                     availableSize = containerSize.height,
                     spacingPerDot = spacing,
                     dotCount = rowCount,
-                    isCentered = layout.isCenteredVertically,
+                    isCentered = centerDotsVertically,
                 )
             offset = Offset(horizontalOffset, verticalOffset)
             scale = (colCount * spacing) / containerSize.width
@@ -423,10 +435,6 @@
     }
 }
 
-/** Whether the UI should be centered vertically. */
-private val BouncerSceneLayout.isCenteredVertically: Boolean
-    get() = this == BouncerSceneLayout.SPLIT
-
 private const val DOT_DIAMETER_DP = 16
 private const val SELECTED_DOT_DIAMETER_DP = 24
 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 8f5d9f4..f505b90 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -52,7 +52,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
@@ -70,7 +69,7 @@
 @Composable
 fun PinPad(
     viewModel: PinBouncerViewModel,
-    layout: BouncerSceneLayout,
+    verticalSpacing: Dp,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -96,8 +95,8 @@
 
     VerticalGrid(
         columns = columns,
-        verticalSpacing = layout.verticalSpacing,
-        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth),
+        verticalSpacing = verticalSpacing,
+        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
         modifier = modifier,
     ) {
         repeat(9) { index ->
@@ -355,14 +354,6 @@
     return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
 }
 
-/** The width of the grid of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.gridWidth: Dp
-    get() = if (isUseCompactSize) 292.dp else 300.dp
-
-/** The spacing between rows of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.verticalSpacing: Dp
-    get() = if (isUseCompactSize) 8.dp else 12.dp
-
 /** Number of columns in the PIN pad grid. */
 private const val columns = 3
 /** Maximum size (width and height) of each PIN pad button. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 185a06c..d83f3aa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -14,6 +14,7 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -28,15 +29,20 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.FixedSizeEdgeDetector
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.transform
 
 object Communal {
@@ -60,7 +66,7 @@
  * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
  * handling and transitions before the full Flexiglass layout is ready.
  */
-@OptIn(ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class)
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
@@ -81,6 +87,15 @@
         return
     }
 
+    // This effect exposes the SceneTransitionLayout's observable transition state to the rest of
+    // the system, and unsets it when the view is disposed to avoid a memory leak.
+    DisposableEffect(viewModel, sceneTransitionLayoutState) {
+        viewModel.setTransitionState(
+            sceneTransitionLayoutState.observableTransitionState().map { it.toModel() }
+        )
+        onDispose { viewModel.setTransitionState(null) }
+    }
+
     Box(modifier = modifier.fillMaxSize()) {
         SceneTransitionLayout(
             modifier = Modifier.fillMaxSize(),
@@ -171,18 +186,40 @@
     Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
 }
 
-// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI.
+// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
 object TransitionSceneKey {
     val Blank = CommunalSceneKey.Blank.toTransitionSceneKey()
     val Communal = CommunalSceneKey.Communal.toTransitionSceneKey()
 }
 
+// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
+fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
+    return this.identity as CommunalSceneKey
+}
+
+// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
 fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
     return SceneKey(name = toString(), identity = this)
 }
 
-fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
-    return this.identity as CommunalSceneKey
+/**
+ * Converts between the [SceneTransitionLayout] state class and our forked data class that can be
+ * used throughout SysUI.
+ */
+// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI.
+fun ObservableTransitionState.toModel(): ObservableCommunalTransitionState {
+    return when (this) {
+        is ObservableTransitionState.Idle ->
+            ObservableCommunalTransitionState.Idle(scene.toCommunalSceneKey())
+        is ObservableTransitionState.Transition ->
+            ObservableCommunalTransitionState.Transition(
+                fromScene = fromScene.toCommunalSceneKey(),
+                toScene = toScene.toCommunalSceneKey(),
+                progress = progress,
+                isInitiatedByUserInput = isInitiatedByUserInput,
+                isUserInputOngoing = isUserInputOngoing,
+            )
+    }
 }
 
 object ContainerDimensions {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 2b11952..31604a6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -213,22 +213,11 @@
     override fun onDetach() {
         super.onDetach()
         removeNodeFromSceneValues()
+        maybePruneMaps(layoutImpl, element, sceneValues)
     }
 
     private fun removeNodeFromSceneValues() {
         sceneValues.nodes.remove(this)
-
-        // If element is not composed from this scene anymore, remove the scene values. This works
-        // because [onAttach] is called before [onDetach], so if an element is moved from the UI
-        // tree we will first add the new code location then remove the old one.
-        if (sceneValues.nodes.isEmpty()) {
-            element.sceneValues.remove(sceneValues.scene)
-        }
-
-        // If the element is not composed in any scene, remove it from the elements map.
-        if (element.sceneValues.isEmpty()) {
-            layoutImpl.elements.remove(element.key)
-        }
     }
 
     fun update(
@@ -237,12 +226,16 @@
         element: Element,
         sceneValues: Element.TargetValues,
     ) {
+        check(layoutImpl == this.layoutImpl && scene == this.scene)
         removeNodeFromSceneValues()
-        this.layoutImpl = layoutImpl
-        this.scene = scene
+
+        val prevElement = this.element
+        val prevSceneValues = this.sceneValues
         this.element = element
         this.sceneValues = sceneValues
+
         addNodeToSceneValues()
+        maybePruneMaps(layoutImpl, prevElement, prevSceneValues)
     }
 
     override fun ContentDrawScope.draw() {
@@ -261,6 +254,28 @@
             }
         }
     }
+
+    companion object {
+        private fun maybePruneMaps(
+            layoutImpl: SceneTransitionLayoutImpl,
+            element: Element,
+            sceneValues: Element.TargetValues,
+        ) {
+            // If element is not composed from this scene anymore, remove the scene values. This
+            // works because [onAttach] is called before [onDetach], so if an element is moved from
+            // the UI tree we will first add the new code location then remove the old one.
+            if (
+                sceneValues.nodes.isEmpty() && element.sceneValues[sceneValues.scene] == sceneValues
+            ) {
+                element.sceneValues.remove(sceneValues.scene)
+
+                // If the element is not composed in any scene, remove it from the elements map.
+                if (element.sceneValues.isEmpty() && layoutImpl.elements[element.key] == element) {
+                    layoutImpl.elements.remove(element.key)
+                }
+            }
+        }
+    }
 }
 
 private fun shouldDrawElement(
@@ -615,7 +630,9 @@
     val toValues = element.sceneValues[toScene]
 
     if (fromValues == null && toValues == null) {
-        error("This should not happen, element $element is neither in $fromScene or $toScene")
+        // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
+        // run anymore.
+        return lastValue()
     }
 
     // The element is shared: interpolate between the value in fromScene and the value in toScene.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index ded6cc1..32025b4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -73,37 +73,37 @@
 internal fun Modifier.nestedScrollToScene(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) =
     this then
         NestedScrollToSceneElement(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
 private data class NestedScrollToSceneElement(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val orientation: Orientation,
-    private val startBehavior: NestedScrollBehavior,
-    private val endBehavior: NestedScrollBehavior,
+    private val topOrLeftBehavior: NestedScrollBehavior,
+    private val bottomOrRightBehavior: NestedScrollBehavior,
 ) : ModifierNodeElement<NestedScrollToSceneNode>() {
     override fun create() =
         NestedScrollToSceneNode(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
     override fun update(node: NestedScrollToSceneNode) {
         node.update(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
     }
 
@@ -111,23 +111,23 @@
         name = "nestedScrollToScene"
         properties["layoutImpl"] = layoutImpl
         properties["orientation"] = orientation
-        properties["startBehavior"] = startBehavior
-        properties["endBehavior"] = endBehavior
+        properties["topOrLeftBehavior"] = topOrLeftBehavior
+        properties["bottomOrRightBehavior"] = bottomOrRightBehavior
     }
 }
 
 private class NestedScrollToSceneNode(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) : DelegatingNode() {
     private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
         scenePriorityNestedScrollConnection(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
     private var nestedScrollNode: DelegatableNode =
@@ -148,8 +148,8 @@
     fun update(
         layoutImpl: SceneTransitionLayoutImpl,
         orientation: Orientation,
-        startBehavior: NestedScrollBehavior,
-        endBehavior: NestedScrollBehavior,
+        topOrLeftBehavior: NestedScrollBehavior,
+        bottomOrRightBehavior: NestedScrollBehavior,
     ) {
         // Clean up the old nested scroll connection
         priorityNestedScrollConnection.reset()
@@ -160,8 +160,8 @@
             scenePriorityNestedScrollConnection(
                 layoutImpl = layoutImpl,
                 orientation = orientation,
-                startBehavior = startBehavior,
-                endBehavior = endBehavior,
+                topOrLeftBehavior = topOrLeftBehavior,
+                bottomOrRightBehavior = bottomOrRightBehavior,
             )
         nestedScrollNode =
             nestedScrollModifierNode(
@@ -175,12 +175,12 @@
 private fun scenePriorityNestedScrollConnection(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) =
     SceneNestedScrollHandler(
             gestureHandler = layoutImpl.gestureHandler(orientation = orientation),
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
         .connection
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index f5561cb..6a7a3a0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -86,16 +86,26 @@
         return element(layoutImpl, scene, key)
     }
 
-    override fun Modifier.nestedScrollToScene(
-        orientation: Orientation,
-        startBehavior: NestedScrollBehavior,
-        endBehavior: NestedScrollBehavior,
+    override fun Modifier.horizontalNestedScrollToScene(
+        leftBehavior: NestedScrollBehavior,
+        rightBehavior: NestedScrollBehavior,
     ): Modifier =
         nestedScrollToScene(
             layoutImpl = layoutImpl,
-            orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            orientation = Orientation.Horizontal,
+            topOrLeftBehavior = leftBehavior,
+            bottomOrRightBehavior = rightBehavior,
+        )
+
+    override fun Modifier.verticalNestedScrollToScene(
+        topBehavior: NestedScrollBehavior,
+        bottomBehavior: NestedScrollBehavior
+    ): Modifier =
+        nestedScrollToScene(
+            layoutImpl = layoutImpl,
+            orientation = Orientation.Vertical,
+            topOrLeftBehavior = topBehavior,
+            bottomOrRightBehavior = bottomBehavior,
         )
 
     @Composable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 212c9eb6..03f37d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -494,9 +494,9 @@
 }
 
 internal class SceneNestedScrollHandler(
-    private val gestureHandler: SceneGestureHandler,
-    private val startBehavior: NestedScrollBehavior,
-    private val endBehavior: NestedScrollBehavior,
+        private val gestureHandler: SceneGestureHandler,
+        private val topOrLeftBehavior: NestedScrollBehavior,
+        private val bottomOrRightBehavior: NestedScrollBehavior,
 ) : NestedScrollHandler {
     override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
@@ -565,8 +565,8 @@
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val behavior: NestedScrollBehavior =
                     when {
-                        offsetAvailable > 0f -> startBehavior
-                        offsetAvailable < 0f -> endBehavior
+                        offsetAvailable > 0f -> topOrLeftBehavior
+                        offsetAvailable < 0f -> bottomOrRightBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
@@ -594,8 +594,8 @@
             canStartPostFling = { velocityAvailable ->
                 val behavior: NestedScrollBehavior =
                     when {
-                        velocityAvailable > 0f -> startBehavior
-                        velocityAvailable < 0f -> endBehavior
+                        velocityAvailable > 0f -> topOrLeftBehavior
+                        velocityAvailable < 0f -> bottomOrRightBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
@@ -604,6 +604,7 @@
                 behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
             },
             canContinueScroll = { true },
+            canScrollOnFling = false,
             onStart = { offsetAvailable ->
                 gestureHandler.gestureWithPriority = this
                 gestureHandler.onDragStarted(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index afa184b..239971f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -126,14 +126,24 @@
      * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable
      * component.
      *
-     * @param orientation is used to determine if we handle top/bottom or left/right events.
-     * @param startBehavior when we should perform the overscroll animation at the top/left.
-     * @param endBehavior when we should perform the overscroll animation at the bottom/right.
+     * @param leftBehavior when we should perform the overscroll animation at the left.
+     * @param rightBehavior when we should perform the overscroll animation at the right.
      */
-    fun Modifier.nestedScrollToScene(
-        orientation: Orientation,
-        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
-        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+    fun Modifier.horizontalNestedScrollToScene(
+        leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+    ): Modifier
+
+    /**
+     * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable
+     * component.
+     *
+     * @param topBehavior when we should perform the overscroll animation at the top.
+     * @param bottomBehavior when we should perform the overscroll animation at the bottom.
+     */
+    fun Modifier.verticalNestedScrollToScene(
+        topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
     ): Modifier
 
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index f820074..dfa2a9a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -226,12 +226,17 @@
     )
 
     /**
-     * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor]
-     * .
+     * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as
+     * [anchor].
      *
      * Note: This currently only works if [anchor] is a shared element of this transition.
      */
-    fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey)
+    fun anchoredSize(
+        matcher: ElementMatcher,
+        anchor: ElementKey,
+        anchorWidth: Boolean = true,
+        anchorHeight: Boolean = true,
+    )
 }
 
 /** The edge of a [SceneTransitionLayout]. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 8c0a5a3..8f4a36e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -178,7 +178,12 @@
         transformation(DrawScale(matcher, scaleX, scaleY, pivot))
     }
 
-    override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) {
-        transformation(AnchoredSize(matcher, anchor))
+    override fun anchoredSize(
+        matcher: ElementMatcher,
+        anchor: ElementKey,
+        anchorWidth: Boolean,
+        anchorHeight: Boolean,
+    ) {
+        transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 95385d5..40c814e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -29,6 +29,8 @@
 internal class AnchoredSize(
     override val matcher: ElementMatcher,
     private val anchor: ElementKey,
+    private val anchorWidth: Boolean,
+    private val anchorHeight: Boolean,
 ) : PropertyTransformation<IntSize> {
     override fun transform(
         layoutImpl: SceneTransitionLayoutImpl,
@@ -41,7 +43,10 @@
         fun anchorSizeIn(scene: SceneKey): IntSize {
             val size = layoutImpl.elements[anchor]?.sceneValues?.get(scene)?.targetSize
             return if (size != null && size != Element.SizeUnspecified) {
-                size
+                IntSize(
+                    width = if (anchorWidth) size.width else value.width,
+                    height = if (anchorHeight) size.height else value.height,
+                )
             } else {
                 value
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index cea8d9a..2c96d0e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -16,9 +16,8 @@
 
 package com.android.compose.nestedscroll
 
-import androidx.compose.ui.geometry.Offset
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 
 /**
  * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
@@ -34,59 +33,44 @@
  *
  * @sample com.android.compose.animation.scene.demo.Shade
  */
-class LargeTopAppBarNestedScrollConnection(
-    private val height: () -> Float,
-    private val onChangeHeight: (Float) -> Unit,
-    private val minHeight: Float,
-    private val maxHeight: Float,
-) : NestedScrollConnection {
-
-    constructor(
-        height: () -> Float,
-        onHeightChanged: (Float) -> Unit,
-        heightRange: ClosedFloatingPointRange<Float>,
-    ) : this(
-        height = height,
-        onChangeHeight = onHeightChanged,
-        minHeight = heightRange.start,
-        maxHeight = heightRange.endInclusive,
+fun LargeTopAppBarNestedScrollConnection(
+    height: () -> Float,
+    onHeightChanged: (Float) -> Unit,
+    heightRange: ClosedFloatingPointRange<Float>,
+): PriorityNestedScrollConnection {
+    val minHeight = heightRange.start
+    val maxHeight = heightRange.endInclusive
+    return PriorityNestedScrollConnection(
+        orientation = Orientation.Vertical,
+        // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
+        // expand. Then, you can then scroll down the content.
+        canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight
+        },
+        // When swiping down, the content will scroll up until it reaches the top. Then, the
+        // LargeTopAppBar will expand until it reaches its [maxHeight].
+        canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight },
+        canStartPostFling = { false },
+        canContinueScroll = {
+            val currentHeight = height()
+            minHeight < currentHeight && currentHeight < maxHeight
+        },
+        canScrollOnFling = true,
+        onStart = { /* do nothing */},
+        onScroll = { offsetAvailable ->
+            val currentHeight = height()
+            val amountConsumed =
+                if (offsetAvailable > 0) {
+                    val amountLeft = maxHeight - currentHeight
+                    offsetAvailable.coerceAtMost(amountLeft)
+                } else {
+                    val amountLeft = minHeight - currentHeight
+                    offsetAvailable.coerceAtLeast(amountLeft)
+                }
+            onHeightChanged(currentHeight + amountConsumed)
+            amountConsumed
+        },
+        // Don't consume the velocity on pre/post fling
+        onStop = { 0f },
     )
-
-    /**
-     * When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will expand.
-     * Then, you can then scroll down the content.
-     */
-    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
-        val y = available.y
-        val currentHeight = height()
-        if (y >= 0 || currentHeight <= minHeight) {
-            return Offset.Zero
-        }
-
-        val amountLeft = minHeight - currentHeight
-        val amountConsumed = y.coerceAtLeast(amountLeft)
-        onChangeHeight(currentHeight + amountConsumed)
-        return Offset(0f, amountConsumed)
-    }
-
-    /**
-     * When swiping down, the content will scroll up until it reaches the top. Then, the
-     * LargeTopAppBar will expand until it reaches its [maxHeight].
-     */
-    override fun onPostScroll(
-        consumed: Offset,
-        available: Offset,
-        source: NestedScrollSource
-    ): Offset {
-        val y = available.y
-        val currentHeight = height()
-        if (y <= 0 || currentHeight >= maxHeight) {
-            return Offset.Zero
-        }
-
-        val amountLeft = maxHeight - currentHeight
-        val amountConsumed = y.coerceAtMost(amountLeft)
-        onChangeHeight(currentHeight + amountConsumed)
-        return Offset(0f, amountConsumed)
-    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index c49a2b8..2841bcf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -38,6 +38,7 @@
     private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
     private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
     private val canContinueScroll: () -> Boolean,
+    private val canScrollOnFling: Boolean,
     private val onStart: (offsetAvailable: Offset) -> Unit,
     private val onScroll: (offsetAvailable: Offset) -> Offset,
     private val onStop: (velocityAvailable: Velocity) -> Velocity,
@@ -59,7 +60,7 @@
 
         if (
             isPriorityMode ||
-                source == NestedScrollSource.Fling ||
+                (source == NestedScrollSource.Fling && !canScrollOnFling) ||
                 !canStartPostScroll(available, offsetBeforeStart)
         ) {
             // The priority mode cannot start so we won't consume the available offset.
@@ -71,7 +72,7 @@
 
     override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
         if (!isPriorityMode) {
-            if (source != NestedScrollSource.Fling) {
+            if (source != NestedScrollSource.Fling || canScrollOnFling) {
                 if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
                     return onPriorityStart(available)
                 }
@@ -98,12 +99,20 @@
     }
 
     override suspend fun onPreFling(available: Velocity): Velocity {
+        if (isPriorityMode && canScrollOnFling) {
+            // We don't want to consume the velocity, we prefer to continue receiving scroll events.
+            return Velocity.Zero
+        }
         // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
         // of the fling gesture.
         return onPriorityStop(velocity = available)
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        if (isPriorityMode) {
+            return onPriorityStop(velocity = available)
+        }
+
         if (!canStartPostFling(available)) {
             return Velocity.Zero
         }
@@ -156,6 +165,7 @@
     canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
     canStartPostFling: (velocityAvailable: Float) -> Boolean,
     canContinueScroll: () -> Boolean,
+    canScrollOnFling: Boolean,
     onStart: (offsetAvailable: Float) -> Unit,
     onScroll: (offsetAvailable: Float) -> Float,
     onStop: (velocityAvailable: Float) -> Float,
@@ -172,6 +182,7 @@
                 canStartPostFling(velocityAvailable.toFloat())
             },
             canContinueScroll = canContinueScroll,
+            canScrollOnFling = canScrollOnFling,
             onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
             onScroll = { offsetAvailable: Offset ->
                 onScroll(offsetAvailable.toFloat()).toOffset()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index ce3e1db..439dc00 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -17,16 +17,21 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -36,6 +41,9 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
@@ -430,6 +438,97 @@
     }
 
     @Test
+    @OptIn(ExperimentalFoundationApi::class)
+    fun elementModifierNodeIsRecycledInLazyLayouts() = runTest {
+        val nPages = 2
+        val pagerState = PagerState(currentPage = 0) { nPages }
+        var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+
+        // This is how we scroll a pager inside a test, as explained in b/315457147#comment2.
+        lateinit var scrollScope: CoroutineScope
+        fun scrollToPage(page: Int) {
+            var animationFinished by mutableStateOf(false)
+            rule.runOnIdle {
+                scrollScope.launch {
+                    pagerState.scrollToPage(page)
+                    animationFinished = true
+                }
+            }
+            rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
+        }
+
+        rule.setContent {
+            scrollScope = rememberCoroutineScope()
+
+            SceneTransitionLayoutForTesting(
+                currentScene = TestScenes.SceneA,
+                onChangeScene = {},
+                transitions = remember { transitions {} },
+                state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
+                edgeDetector = DefaultEdgeDetector,
+                modifier = Modifier,
+                transitionInterceptionThreshold = 0f,
+                onLayoutImpl = { nullableLayoutImpl = it },
+            ) {
+                scene(TestScenes.SceneA) {
+                    // The pages are full-size and beyondBoundsPageCount is 0, so at rest only one
+                    // page should be composed.
+                    HorizontalPager(
+                        pagerState,
+                        beyondBoundsPageCount = 0,
+                    ) { page ->
+                        when (page) {
+                            0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
+                            1 -> Box(Modifier.fillMaxSize())
+                            else -> error("page $page < nPages $nPages")
+                        }
+                    }
+                }
+            }
+        }
+
+        assertThat(nullableLayoutImpl).isNotNull()
+        val layoutImpl = nullableLayoutImpl!!
+
+        // There is only Foo in the elements map.
+        assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
+        val element = layoutImpl.elements.getValue(TestElements.Foo)
+        val sceneValues = element.sceneValues
+        assertThat(sceneValues.keys).containsExactly(TestScenes.SceneA)
+
+        // Get the ElementModifier node that should be reused later on when coming back to this
+        // page.
+        val nodes = sceneValues.getValue(TestScenes.SceneA).nodes
+        assertThat(nodes).hasSize(1)
+        val node = nodes.single()
+
+        // Go to the second page.
+        scrollToPage(1)
+        rule.waitForIdle()
+
+        assertThat(nodes).isEmpty()
+        assertThat(sceneValues).isEmpty()
+        assertThat(layoutImpl.elements).isEmpty()
+
+        // Go back to the first page.
+        scrollToPage(0)
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
+        val newElement = layoutImpl.elements.getValue(TestElements.Foo)
+        val newSceneValues = newElement.sceneValues
+        assertThat(newElement).isNotEqualTo(element)
+        assertThat(newSceneValues).isNotEqualTo(sceneValues)
+        assertThat(newSceneValues.keys).containsExactly(TestScenes.SceneA)
+
+        // The ElementModifier node should be the same as before.
+        val newNodes = newSceneValues.getValue(TestScenes.SceneA).nodes
+        assertThat(newNodes).hasSize(1)
+        val newNode = newNodes.single()
+        assertThat(newNode).isSameInstanceAs(node)
+    }
+
+    @Test
     fun existingElementsDontRecomposeWhenTransitionStateChanges() {
         var fooCompositions = 0
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index aa942e0..34afc4c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -117,8 +117,8 @@
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
             SceneNestedScrollHandler(
                     gestureHandler = sceneGestureHandler,
-                    startBehavior = nestedScrollBehavior,
-                    endBehavior = nestedScrollBehavior,
+                    topOrLeftBehavior = nestedScrollBehavior,
+                    bottomOrRightBehavior = nestedScrollBehavior,
                 )
                 .connection
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
index 8ef6757..e555a01 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -85,4 +85,50 @@
             after { onElement(TestElements.Bar).assertDoesNotExist() }
         }
     }
+
+    @Test
+    fun testAnchoredWidthOnly() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar))
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo, anchorHeight = false)
+            },
+        ) {
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 60.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 60.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 60.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 60.dp) }
+            at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+            after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+        }
+    }
+
+    @Test
+    fun testAnchoredHeightOnly() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar))
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo, anchorWidth = false)
+            },
+        ) {
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 100.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 90.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 80.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 70.dp) }
+            at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+            after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index 03d231a..e2974cd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -17,6 +17,7 @@
 package com.android.compose.nestedscroll
 
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -36,6 +37,15 @@
             heightRange = heightRange,
         )
 
+    private fun NestedScrollConnection.scroll(
+        available: Offset,
+        consumedByScroll: Offset = Offset.Zero,
+    ) {
+        val consumedByPreScroll = onPreScroll(available = available, source = scrollSource)
+        val consumed = consumedByPreScroll + consumedByScroll
+        onPostScroll(consumed = consumed, available = available - consumed, source = scrollSource)
+    }
+
     @Test
     fun onScrollUp_consumeHeightFirst() {
         val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
@@ -50,6 +60,41 @@
     }
 
     @Test
+    fun onScrollUpAfterContentScrolled_ignoreUpEvent() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        // scroll down consumed by a child
+        scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f))
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+        // It should ignore all onPreScroll events
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(height).isEqualTo(1f)
+    }
+
+    @Test
+    fun onScrollUpAfterContentReturnedToZero_consumeHeight() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        // scroll down consumed by a child
+        scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f))
+
+        // scroll up consumed by a child, the child is in its original position
+        scrollConnection.scroll(available = Offset(0f, -1f), consumedByScroll = Offset(0f, -1f))
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+        // It should ignore all onPreScroll events
+        assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f))
+        assertThat(height).isEqualTo(0f)
+    }
+
+    @Test
     fun onScrollUp_consumeDownToMin() {
         val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
         height = 0f
@@ -110,6 +155,27 @@
     }
 
     @Test
+    fun onScrollDownAfterPostScroll_consumeHeightPreScroll() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+        scrollConnection.onPostScroll(
+            consumed = Offset.Zero,
+            available = Offset(x = 0f, y = 0.5f),
+            source = scrollSource
+        )
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = Offset(x = 0f, y = 0.5f),
+                source = scrollSource
+            )
+        assertThat(offsetConsumed).isEqualTo(Offset(0f, 0.5f))
+
+        // It can increase by 1 (0.5f + 0.5f) the height
+        assertThat(height).isEqualTo(2f)
+    }
+
+    @Test
     fun onScrollDown_consumeUpToMax() {
         val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
         height = 2f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 122774b..8a9a92e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -46,6 +46,7 @@
             canStartPostScroll = { _, _ -> canStartPostScroll },
             canStartPostFling = { canStartPostFling },
             canContinueScroll = { canContinueScroll },
+            canScrollOnFling = false,
             onStart = { isStarted = true },
             onScroll = {
                 lastScroll = it
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index cbb772f..0ab596c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
@@ -56,10 +58,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -539,4 +543,77 @@
                 .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE))
             job.cancel()
         }
+
+    @Test
+    fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForLockscreenAodTransitions(this)
+            // WHEN lockscreen to aod transition is cancelled
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.CANCELED
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is NOT updated to zero
+            verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt())
+            job.cancel()
+        }
+
+    @Test
+    fun dreamingToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForDreamingToAodTransitions(this)
+            // WHEN dreaming to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+            job.cancel()
+        }
+
+    @Test
+    fun alternateBouncerToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForAlternateBouncerToAodTransitions(this)
+            // WHEN alternate bouncer to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView)
+                .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
+            job.cancel()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 7196de6..65176e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -29,6 +30,8 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -40,29 +43,30 @@
 class CommunalRepositoryImplTest : SysuiTestCase() {
     private lateinit var underTest: CommunalRepositoryImpl
 
-    private lateinit var testScope: TestScope
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
-    private lateinit var sceneContainerFlags: FakeSceneContainerFlags
     private lateinit var sceneContainerRepository: SceneContainerRepository
 
     @Before
     fun setUp() {
-        testScope = TestScope()
-
         val sceneTestUtils = SceneTestUtils(this)
-        sceneContainerFlags = FakeSceneContainerFlags(enabled = false)
         sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
         featureFlagsClassic = FakeFeatureFlagsClassic()
 
         featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
 
-        underTest =
-            CommunalRepositoryImpl(
-                featureFlagsClassic,
-                sceneContainerFlags,
-                sceneContainerRepository,
-            )
+        underTest = createRepositoryImpl(false)
+    }
+
+    private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
+        return CommunalRepositoryImpl(
+            testScope.backgroundScope,
+            featureFlagsClassic,
+            FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+            sceneContainerRepository,
+        )
     }
 
     @Test
@@ -86,13 +90,7 @@
     @Test
     fun isCommunalShowing_sceneContainerEnabled_onCommunalScene_true() =
         testScope.runTest {
-            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
-            underTest =
-                CommunalRepositoryImpl(
-                    featureFlagsClassic,
-                    sceneContainerFlags,
-                    sceneContainerRepository,
-                )
+            underTest = createRepositoryImpl(true)
 
             sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Communal))
 
@@ -103,17 +101,49 @@
     @Test
     fun isCommunalShowing_sceneContainerEnabled_onLockscreenScene_false() =
         testScope.runTest {
-            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
-            underTest =
-                CommunalRepositoryImpl(
-                    featureFlagsClassic,
-                    sceneContainerFlags,
-                    sceneContainerRepository,
-                )
+            underTest = createRepositoryImpl(true)
 
             sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Lockscreen))
 
             val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
             assertThat(isCommunalHubShowing).isFalse()
         }
+
+    @Test
+    fun transitionState_idleByDefault() =
+        testScope.runTest {
+            val transitionState by collectLastValue(underTest.transitionState)
+            assertThat(transitionState)
+                .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
+        }
+
+    @Test
+    fun transitionState_setTransitionState_returnsNewValue() =
+        testScope.runTest {
+            val expectedSceneKey = CommunalSceneKey.Communal
+            underTest.setTransitionState(
+                flowOf(ObservableCommunalTransitionState.Idle(expectedSceneKey))
+            )
+
+            val transitionState by collectLastValue(underTest.transitionState)
+            assertThat(transitionState)
+                .isEqualTo(ObservableCommunalTransitionState.Idle(expectedSceneKey))
+        }
+
+    @Test
+    fun transitionState_setNullTransitionState_returnsDefaultValue() =
+        testScope.runTest {
+            // Set a value for the transition state flow.
+            underTest.setTransitionState(
+                flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal))
+            )
+
+            // Set the transition state flow back to null.
+            underTest.setTransitionState(null)
+
+            // Flow returns default scene key.
+            val transitionState by collectLastValue(underTest.transitionState)
+            assertThat(transitionState)
+                .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 16cfa23..1f8e29a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -161,7 +161,7 @@
             whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
 
             val targets = listOf(target1, target2, target3)
-            smartspaceRepository.setLockscreenSmartspaceTargets(targets)
+            smartspaceRepository.setCommunalSmartspaceTargets(targets)
 
             val smartspaceContent by collectLastValue(underTest.smartspaceContent)
             assertThat(smartspaceContent?.size).isEqualTo(1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 8896e6e..314dfdf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -116,7 +116,7 @@
             whenever(target.smartspaceTargetId).thenReturn("target")
             whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
             whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
             mediaRepository.mediaPlaying.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7fbcae0..8a71168 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -135,7 +135,7 @@
             whenever(target.smartspaceTargetId).thenReturn("target")
             whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
             whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
             mediaRepository.mediaPlaying.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 058b35e..34f703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -344,6 +344,31 @@
         }
 
     @Test
+    fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
+        testScope.runTest {
+            whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+            repository.setKeyguardShowing(false)
+            repository.setIsDozing(true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+
+            val collectedValue by
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        KeyguardQuickAffordancePosition.BOTTOM_START
+                    )
+                )
+
+            assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+        }
+
+    @Test
     fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() =
         testScope.runTest {
             repository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS
new file mode 100644
index 0000000..cd04e82
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /packages/SystemUI/src/com/android/systemui/qs/OWNERS
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
new file mode 100644
index 0000000..30d1822
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.restoreprocessors
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class WorkTileRestoreProcessorTest : SysuiTestCase() {
+
+    private val underTest = WorkTileRestoreProcessor()
+    @Test
+    fun restoreWithWorkTile_removeTracking() = runTest {
+        val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = listOf(TILE_SPEC),
+                restoredAutoAddedTiles = setOf(TILE_SPEC),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isEqualTo(Unit)
+    }
+
+    @Test
+    fun restoreWithWorkTile_otherUser_noRemoveTracking() = runTest {
+        val removeTracking by
+            collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER + 1)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = listOf(TILE_SPEC),
+                restoredAutoAddedTiles = setOf(TILE_SPEC),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isNull()
+    }
+
+    @Test
+    fun restoreWithoutWorkTile_noSignal() = runTest {
+        val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = emptyList(),
+                restoredAutoAddedTiles = emptySet(),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isNull()
+    }
+
+    companion object {
+        private const val USER = 10
+        private val TILE_SPEC = TileSpec.Companion.create("work")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
index adccc84..c7e7845 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -25,6 +25,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -32,25 +37,28 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WorkTileAutoAddableTest : SysuiTestCase() {
 
+    private val kosmos = Kosmos()
+
+    private val restoreProcessor: RestoreProcessor
+        get() = kosmos.workTileRestoreProcessor
+
     private lateinit var userTracker: FakeUserTracker
 
     private lateinit var underTest: WorkTileAutoAddable
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         userTracker =
             FakeUserTracker(
                 _userId = USER_INFO_0.id,
@@ -58,7 +66,7 @@
                 _userProfiles = listOf(USER_INFO_0)
             )
 
-        underTest = WorkTileAutoAddable(userTracker)
+        underTest = WorkTileAutoAddable(userTracker, kosmos.workTileRestoreProcessor)
     }
 
     @Test
@@ -114,10 +122,80 @@
         assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
     }
 
+    @Test
+    fun restoreDataWithWorkTile_noCurrentManagedProfile_triggersRemove() = runTest {
+        val userId = 0
+        val signal by collectLastValue(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signal!!).isEqualTo(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithWorkTile_currentlyManagedProfile_doesntTriggerRemove() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithoutWorkTile_noManagedProfile_doesntTriggerRemove() = runTest {
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithoutWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithoutWorkTile_managedProfile_doesntTriggerRemove() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithoutWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
     companion object {
         private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
         private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
         private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
         private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+
+        private fun createRestoreWithWorkTile(userId: Int): RestoreData {
+            return RestoreData(
+                listOf(TileSpec.create("a"), SPEC, TileSpec.create("b")),
+                setOf(SPEC),
+                userId,
+            )
+        }
+
+        private fun createRestoreWithoutWorkTile(userId: Int): RestoreData {
+            return RestoreData(
+                listOf(TileSpec.create("a"), TileSpec.create("b")),
+                emptySet(),
+                userId,
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 41a7ec0..54b03a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -183,6 +183,22 @@
             assertThat(autoAddedTiles).contains(SPEC)
         }
 
+    @Test
+    fun autoAddable_removeTrackingSignal_notRemovedButUnmarked() =
+        testScope.runTest {
+            autoAddRepository.markTileAdded(USER, SPEC)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendRemoveTrackingSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).removeTiles(any())
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
     private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
         return AutoAddInteractor(
                 autoAddables,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
index f73cab8..b2a9783 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -5,10 +5,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.POSTPROCESS
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.PREPROCESS
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -17,7 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.inOrder
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -28,6 +33,9 @@
 
     private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository()
 
+    private val restoreProcessor: TestableRestoreProcessor = TestableRestoreProcessor()
+    private val qsLogger: QSPipelineLogger = mock()
+
     private lateinit var underTest: RestoreReconciliationInteractor
 
     private val testDispatcher = StandardTestDispatcher()
@@ -35,13 +43,13 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
         underTest =
             RestoreReconciliationInteractor(
                 tileSpecRepository,
                 autoAddRepository,
                 qsSettingsRestoredRepository,
+                setOf(restoreProcessor),
+                qsLogger,
                 testScope.backgroundScope,
                 testDispatcher
             )
@@ -85,6 +93,44 @@
             assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet())
         }
 
+    @Test
+    fun restoreProcessorsCalled() =
+        testScope.runTest {
+            val user = 10
+
+            val restoredSpecs = "a,c,d,f"
+            val restoredAutoAdded = "d,e"
+
+            val restoreData =
+                RestoreData(
+                    restoredSpecs.toTilesList(),
+                    restoredAutoAdded.toTilesSet(),
+                    user,
+                )
+
+            qsSettingsRestoredRepository.onDataRestored(restoreData)
+            runCurrent()
+
+            assertThat(restoreProcessor.calls).containsExactly(PREPROCESS, POSTPROCESS).inOrder()
+        }
+
+    private class TestableRestoreProcessor : RestoreProcessor {
+        val calls = mutableListOf<Any>()
+
+        override suspend fun preProcessRestore(restoreData: RestoreData) {
+            calls.add(PREPROCESS)
+        }
+
+        override suspend fun postProcessRestore(restoreData: RestoreData) {
+            calls.add(POSTPROCESS)
+        }
+
+        companion object {
+            val PREPROCESS = Any()
+            val POSTPROCESS = Any()
+        }
+    }
+
     companion object {
         private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
         private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
new file mode 100644
index 0000000..96d5774
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This integration test is for testing the solution to b/314781280. In particular, there are two
+ * issues we want to verify after a restore of a device with a work profile and a work mode tile:
+ * * When the work profile is re-enabled in the target device, it is auto-added.
+ * * The tile is auto-added in the same position that it was in the restored device.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+    // Getter here so it can change when there is a managed profile.
+    private val workTileAvailable: Boolean
+        get() = hasManagedProfile()
+    private val currentUser: Int
+        get() = kosmos.userTracker.userId
+
+    private val testScope: TestScope
+        get() = kosmos.testScope
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
+
+        kosmos.qsTileFactory = FakeQSFactory(::tileCreator)
+        kosmos.restoreReconciliationInteractor.start()
+        kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor)
+    }
+
+    @Test
+    fun workTileRestoredAndPreviouslyAutoAdded_notAvailable_willBeAutoaddedInCorrectPosition() =
+        testScope.runTest {
+            val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles)
+
+            // Set up
+            val currentTiles = listOf("a".toTileSpec())
+            kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles)
+
+            val restoredTiles =
+                listOf(WORK_TILE_SPEC) + listOf("b", "c", "d").map { it.toTileSpec() }
+            val restoredAutoAdded = setOf(WORK_TILE_SPEC)
+
+            val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser)
+
+            // WHEN we restore tiles that auto-added the WORK tile and it's not available (there
+            // are no managed profiles)
+            kosmos.fakeRestoreRepository.onDataRestored(restoreData)
+
+            // THEN the work tile is not part of the current tiles
+            assertThat(tiles!!).hasSize(3)
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+
+            // WHEN we add a work profile
+            createManagedProfileAndAdd()
+
+            // THEN the work profile is added in the correct place
+            assertThat(tiles!!.first().spec).isEqualTo(WORK_TILE_SPEC)
+        }
+
+    @Test
+    fun workTileNotRestoredAndPreviouslyAutoAdded_wontBeAutoAddedWhenWorkProfileIsAdded() =
+        testScope.runTest {
+            val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles)
+
+            // Set up
+            val currentTiles = listOf("a".toTileSpec())
+            kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles)
+            runCurrent()
+
+            val restoredTiles = listOf("b", "c", "d").map { it.toTileSpec() }
+            val restoredAutoAdded = setOf(WORK_TILE_SPEC)
+
+            val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser)
+
+            // WHEN we restore tiles that auto-added the WORK tile
+            kosmos.fakeRestoreRepository.onDataRestored(restoreData)
+
+            // THEN the work tile is not part of the current tiles
+            assertThat(tiles!!).hasSize(3)
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+
+            // WHEN we add a work profile
+            createManagedProfileAndAdd()
+
+            // THEN the work profile is not added because the user had manually removed it in the
+            // past
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+        }
+
+    private fun tileCreator(spec: String): QSTile {
+        return if (spec == WORK_TILE_SPEC.spec) {
+            FakeQSTile(currentUser, workTileAvailable)
+        } else {
+            FakeQSTile(currentUser)
+        }
+    }
+
+    private fun hasManagedProfile(): Boolean {
+        return kosmos.userTracker.userProfiles.any { it.isManagedProfile }
+    }
+
+    private fun TestScope.createManagedProfileAndAdd() {
+        kosmos.fakeUserTracker.set(
+            listOf(USER_0_INFO, MANAGED_USER_INFO),
+            0,
+        )
+        runCurrent()
+    }
+
+    private companion object {
+        val WORK_TILE_SPEC = "work".toTileSpec()
+        val USER_0_INFO =
+            UserInfo(
+                0,
+                "zero",
+                "",
+                UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+            )
+        val MANAGED_USER_INFO =
+            UserInfo(
+                10,
+                "ten-managed",
+                "",
+                0,
+                UserManager.USER_TYPE_PROFILE_MANAGED,
+            )
+
+        fun String.toTileSpec() = TileSpec.create(this)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 2b744ac..00405d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.alarm.domain
 
 import android.app.AlarmManager
+import android.graphics.drawable.TestStubDrawable
 import android.widget.Switch
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -40,7 +41,14 @@
     private val kosmos = Kosmos()
     private val alarmTileConfig = kosmos.qsAlarmTileConfig
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
-    private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        AlarmTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun notAlarmSet() {
@@ -100,7 +108,7 @@
     ): QSTileState {
         val label = context.getString(R.string.status_bar_alarm)
         return QSTileState(
-            { Icon.Resource(R.drawable.ic_alarm, null) },
+            { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 7b2ac90..b60f483 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
+import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,7 +36,17 @@
 class FlashlightMapperTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsFlashlightTileConfig
-    private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        FlashlightMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_flashlight_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -56,20 +67,20 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
 
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
         val actualIcon = tileState.icon()
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
 
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
         val actualIcon = tileState.icon()
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index 8791877..ea74a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain
 
+import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,7 +37,17 @@
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsLocationTileConfig
 
-    private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        LocationTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_location_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -56,20 +67,18 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
+        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
         val actualIcon = tileState.icon()
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
+        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
         val actualIcon = tileState.icon()
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
new file mode 100644
index 0000000..4b96251
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.SharedPreferences
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import kotlin.coroutines.EmptyCoroutineContext
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+/** Test [DataSaverDialogDelegate]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverDialogDelegateTest : SysuiTestCase() {
+
+    private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+    private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+    private lateinit var sysuiDialog: SystemUIDialog
+    private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate
+
+    @Before
+    fun setup() {
+        sysuiDialog = mock<SystemUIDialog>()
+        sysuiDialogFactory = mock<SystemUIDialog.Factory>()
+
+        dataSaverDialogDelegate =
+            DataSaverDialogDelegate(
+                sysuiDialogFactory,
+                context,
+                EmptyCoroutineContext,
+                dataSaverController,
+                mock<SharedPreferences>()
+            )
+
+        whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context)))
+            .thenReturn(sysuiDialog)
+    }
+    @Test
+    fun delegateSetsDialogTitleCorrectly() {
+        val expectedResId = R.string.data_saver_enable_title
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setTitle(eq(expectedResId))
+    }
+
+    @Test
+    fun delegateSetsDialogMessageCorrectly() {
+        val expectedResId = R.string.data_saver_description
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setMessage(expectedResId)
+    }
+
+    @Test
+    fun delegateSetsDialogPositiveButtonCorrectly() {
+        val expectedResId = R.string.data_saver_enable_button
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setPositiveButton(eq(expectedResId), any())
+    }
+
+    @Test
+    fun delegateSetsDialogCancelButtonCorrectly() {
+        val expectedResId = R.string.cancel
+
+        dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+        verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
new file mode 100644
index 0000000..d162c77
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig
+
+    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+    private val mapper by lazy {
+        DataSaverTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_data_saver_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
+
+    @Test
+    fun activeStateMatchesEnabledModel() {
+        val inputModel = DataSaverTileModel(true)
+
+        val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+        val expectedState =
+            createDataSaverTileState(
+                QSTileState.ActivationState.ACTIVE,
+                R.drawable.qs_data_saver_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun inactiveStateMatchesDisabledModel() {
+        val inputModel = DataSaverTileModel(false)
+
+        val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+        val expectedState =
+            createDataSaverTileState(
+                QSTileState.ActivationState.INACTIVE,
+                R.drawable.qs_data_saver_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createDataSaverTileState(
+        activationState: QSTileState.ActivationState,
+        iconRes: Int
+    ): QSTileState {
+        val label = context.getString(R.string.data_saver)
+        val secondaryLabel =
+            if (activationState == QSTileState.ActivationState.ACTIVE)
+                context.resources.getStringArray(R.array.tile_states_saver)[2]
+            else if (activationState == QSTileState.ActivationState.INACTIVE)
+                context.resources.getStringArray(R.array.tile_states_saver)[1]
+            else context.resources.getStringArray(R.array.tile_states_saver)[0]
+
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..819bd03
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileDataInteractorTest : SysuiTestCase() {
+    private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
+    private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller)
+
+    @Test
+    fun isAvailableRegardlessOfController() = runTest {
+        controller.setDataSaverEnabled(false)
+
+        runCurrent()
+        val availability by collectLastValue(underTest.availability(TEST_USER))
+
+        Truth.assertThat(availability).isTrue()
+    }
+
+    @Test
+    fun dataMatchesController() = runTest {
+        controller.setDataSaverEnabled(false)
+        val flowValues: List<DataSaverTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.setDataSaverEnabled(true)
+        runCurrent()
+        controller.setDataSaverEnabled(false)
+        runCurrent()
+
+        Truth.assertThat(flowValues.size).isEqualTo(3)
+        Truth.assertThat(flowValues.map { it.isEnabled })
+            .containsExactly(false, true, false)
+            .inOrder()
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..7091cb3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileUserActionInteractorTest : SysuiTestCase() {
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+    private lateinit var userFileManager: UserFileManager
+    private lateinit var sharedPreferences: SharedPreferences
+    private lateinit var dialogFactory: SystemUIDialog.Factory
+    private lateinit var underTest: DataSaverTileUserActionInteractor
+
+    @Before
+    fun setup() {
+        userFileManager = mock<UserFileManager>()
+        sharedPreferences = mock<SharedPreferences>()
+        dialogFactory = mock<SystemUIDialog.Factory>()
+        whenever(
+                userFileManager.getSharedPreferences(
+                    eq(DataSaverTileUserActionInteractor.PREFS),
+                    eq(Context.MODE_PRIVATE),
+                    eq(context.userId)
+                )
+            )
+            .thenReturn(sharedPreferences)
+
+        underTest =
+            DataSaverTileUserActionInteractor(
+                context,
+                EmptyCoroutineContext,
+                EmptyCoroutineContext,
+                dataSaverController,
+                qsTileIntentUserActionHandler,
+                mock<DialogLaunchAnimator>(),
+                dialogFactory,
+                userFileManager,
+            )
+    }
+
+    /** Since the dialog was shown before, we expect the click to enable the controller. */
+    @Test
+    fun handleClickToEnableDialogShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(true)
+        val stateBeforeClick = false
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick)
+    }
+
+    /**
+     * The first time the tile is clicked to turn on we expect (1) the enabled state to not change
+     * and (2) the dialog to be shown instead.
+     */
+    @Test
+    fun handleClickToEnableDialogNotShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(false)
+        val mockDialog = mock<SystemUIDialog>()
+        whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog)
+        val stateBeforeClick = false
+
+        val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))
+        underTest.handleInput(input)
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick)
+        verify(mockDialog).show()
+    }
+
+    /** Disabling should flip the state, even if the dialog was not shown before. */
+    @Test
+    fun handleClickToDisableDialogNotShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(false)
+        val enabledBeforeClick = true
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+    }
+
+    @Test
+    fun handleClickToDisableDialogShownBefore() = runTest {
+        whenever(
+                sharedPreferences.getBoolean(
+                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+                    any()
+                )
+            )
+            .thenReturn(true)
+        val enabledBeforeClick = true
+
+        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabledState = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+        val actualIntentAction = intentInput.intent.action
+        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabledState = false
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+        val actualIntentAction = intentInput.intent.action
+        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index 87f5009..a977606 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.uimodenight.domain
 
 import android.app.UiModeManager
+import android.graphics.drawable.TestStubDrawable
 import android.text.TextUtils
 import android.view.View
 import android.widget.Switch
@@ -41,7 +42,15 @@
     private val qsTileConfig = kosmos.qsUiModeNightTileConfig
 
     private val mapper by lazy {
-        UiModeNightTileMapper(context.orCreateTestableResources.resources)
+        UiModeNightTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_light_dark_theme_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
     }
 
     private fun createUiNightModeTileState(
@@ -60,7 +69,7 @@
         expandedAccessibilityClass: KClass<out View>? = Switch::class,
     ): QSTileState {
         return QSTileState(
-            { Icon.Resource(iconRes, null) },
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
new file mode 100644
index 0000000..ef2046d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.smartspace
+
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.util.concurrency.Execution
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class CommunalSmartspaceControllerTest : SysuiTestCase() {
+    @Mock private lateinit var smartspaceManager: SmartspaceManager
+
+    @Mock private lateinit var execution: Execution
+
+    @Mock private lateinit var uiExecutor: Executor
+
+    @Mock private lateinit var targetFilter: SmartspaceTargetFilter
+
+    @Mock private lateinit var plugin: BcSmartspaceDataPlugin
+
+    @Mock private lateinit var precondition: SmartspacePrecondition
+
+    @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+    @Mock private lateinit var session: SmartspaceSession
+
+    private lateinit var controller: CommunalSmartspaceController
+
+    // TODO(b/272811280): Remove usage of real view
+    private val fakeParent = FrameLayout(context)
+
+    /**
+     * A class which implements SmartspaceView and extends View. This is mocked to provide the right
+     * object inheritance and interface implementation used in CommunalSmartspaceController
+     */
+    private class TestView(context: Context?) : View(context), SmartspaceView {
+        override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
+
+        override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {}
+
+        override fun setPrimaryTextColor(color: Int) {}
+
+        override fun setUiSurface(uiSurface: String) {}
+
+        override fun setDozeAmount(amount: Float) {}
+
+        override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
+
+        override fun setFalsingManager(falsingManager: FalsingManager?) {}
+
+        override fun setDnd(image: Drawable?, description: String?) {}
+
+        override fun setNextAlarm(image: Drawable?, description: String?) {}
+
+        override fun setMediaTarget(target: SmartspaceTarget?) {}
+
+        override fun getSelectedPage(): Int {
+            return 0
+        }
+
+        override fun getCurrentCardTopPadding(): Int {
+            return 0
+        }
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+
+        controller =
+            CommunalSmartspaceController(
+                context,
+                smartspaceManager,
+                execution,
+                uiExecutor,
+                precondition,
+                Optional.of(targetFilter),
+                Optional.of(plugin)
+            )
+    }
+
+    /** Ensures smartspace session begins on a listener only flow. */
+    @Test
+    fun testConnectOnListen() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        controller.addListener(listener)
+
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        var targetListener =
+            withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+                verify(session).addOnTargetsAvailableListener(any(), capture())
+            }
+
+        `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
+
+        var target = Mockito.mock(SmartspaceTarget::class.java)
+        targetListener.onTargetsAvailable(listOf(target))
+
+        var targets =
+            withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
+
+        assertThat(targets.contains(target)).isTrue()
+
+        controller.removeListener(listener)
+
+        verify(session).close()
+    }
+
+    /**
+     * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+     * view is detached.
+     */
+    @Test
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        controller.addListener(listener)
+
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        controller.removeListener(listener)
+
+        verify(session).close()
+
+        // And the listener receives an empty list of targets and unregisters the notifier
+        verify(plugin).onTargetsAvailable(emptyList())
+        verify(plugin).registerSmartspaceEventNotifier(null)
+    }
+}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 64c0f99..c99cb39 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -44,6 +44,7 @@
     String UI_SURFACE_HOME_SCREEN = "home";
     String UI_SURFACE_MEDIA = "media_data_manager";
     String UI_SURFACE_DREAM = "dream";
+    String UI_SURFACE_GLANCEABLE_HUB = "glanceable_hub";
 
     String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
     int VERSION = 1;
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
deleted file mode 100644
index 02e10cd..0000000
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <shape android:shape="rectangle">
-            <corners android:radius="16dp" />
-            <stroke android:width="3dp"
-                android:color="@color/bouncer_password_focus_color" />
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 0b35559..66c54f2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
         android:layout_marginTop="@dimen/keyguard_lock_padding"
         android:importantForAccessibility="no"
         android:ellipsize="marquee"
-        android:focusable="false"
+        android:focusable="true"
         android:gravity="center"
         android:singleLine="true" />
 </merge>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 6e6709f..88f7bcd 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,6 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
index e297b93..bcbe2c43 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
@@ -17,6 +17,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="56dp"
+    android:paddingTop="4dp"
+    android:paddingBottom="4dp"
     android:layout_marginBottom="4dp"
     android:ellipsize="end"
     android:maxLines="1"
diff --git a/packages/SystemUI/res/layout/privacy_dialog_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
index 843dad0..76098a1 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_v2.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
@@ -16,7 +16,7 @@
 <androidx.core.widget.NestedScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="@dimen/large_dialog_width"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
     <LinearLayout
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index a22fd18..bcc3c83 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,9 +93,6 @@
     <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
     <!-- Color of background circle of user avatars in quick settings user switcher -->
     <color name="qs_user_switcher_avatar_background">#3C4043</color>
-    <!-- Color of border for keyguard password input when focused -->
-    <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
-
 
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 462fc95..5f6a39a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -56,8 +56,6 @@
     <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
     <!-- Color of background circle of user avatars in keyguard user switcher -->
     <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
-    <!-- Color of border for keyguard password input when focused -->
-    <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
 
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index fec96c6..317201d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.shared.rotation;
 
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-
 import android.annotation.DimenRes;
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
@@ -89,8 +87,7 @@
             @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
             @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
             @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
-        mContext = context.createWindowContext(context.getDisplay(), TYPE_NAVIGATION_BAR_PANEL,
-                null);
+        mContext = context;
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null);
         mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index bf68869..d8c1e41 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -103,8 +103,6 @@
     public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
     // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
     public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
-    // The current app is in immersive mode
-    public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
     // The voice interaction session window is showing
     public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
     // Freeform windows are showing in desktop mode
@@ -162,7 +160,6 @@
             SYSUI_STATE_DEVICE_DOZING,
             SYSUI_STATE_BACK_DISABLED,
             SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
-            SYSUI_STATE_IMMERSIVE_MODE,
             SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
             SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
             SYSUI_STATE_DEVICE_DREAMING,
@@ -247,9 +244,6 @@
         if ((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0) {
             str.add("bubbles_mange_menu_expanded");
         }
-        if ((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0) {
-            str.add("immersive_mode");
-        }
         if ((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
             str.add("vis_win_showing");
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 9764de1..36fe75f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -168,6 +168,7 @@
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
+        mPasswordEntry.setDefaultFocusHighlightEnabled(false);
 
         mOkButton = findViewById(R.id.key_enter);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 37bd9b2..9c61a8a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1277,6 +1277,17 @@
 
     private final FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
+                public void onAuthenticatedChanged(boolean isAuthenticated) {
+                    if (!isAuthenticated) {
+                        for (int i = 0; i < mCallbacks.size(); i++) {
+                            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                            if (cb != null) {
+                                cb.onFacesCleared();
+                            }
+                        }
+                    }
+                }
+
                 @Override
                 public void onAuthEnrollmentStateChanged(boolean enrolled) {
                     notifyAboutEnrollmentChange(TYPE_FACE);
@@ -1961,7 +1972,7 @@
 
     protected void handleStartedGoingToSleep(int arg1) {
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -3010,7 +3021,7 @@
     void handleUserSwitching(int userId, Runnable resultCallback) {
         mLogger.logUserSwitching(userId, "from UserTracker");
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
         mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
                 trustUsuallyManaged, "userSwitching");
@@ -3560,25 +3571,30 @@
         return mServiceStates.get(subId);
     }
 
-    public void clearBiometricRecognized() {
-        clearBiometricRecognized(UserHandle.USER_NULL);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognized() {
+        clearFingerprintRecognized(UserHandle.USER_NULL);
     }
 
-    public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) {
-        clearBiometricRecognized(unlockedUser);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) {
+        clearFingerprintRecognized(unlockedUser);
     }
 
-    private void clearBiometricRecognized(int unlockedUser) {
+    private void clearFingerprintRecognized(int unlockedUser) {
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
-        mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
-        mLogger.d("clearBiometricRecognized");
+        mLogger.d("clearFingerprintRecognized");
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricsCleared();
+                cb.onFingerprintsCleared();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 02dd331..9d216dce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,9 +291,14 @@
     public void onLogoutEnabledChanged() { }
 
     /**
-     * Called when authenticated biometrics are cleared.
+     * Called when authenticated fingerprint biometrics are cleared.
      */
-    public void onBiometricsCleared() { }
+    public void onFingerprintsCleared() { }
+
+    /**
+     * Called when authenticated face biometrics have cleared.
+     */
+    public void onFacesCleared() { }
 
     /**
      * Called when the secondary lock screen requirement changes.
diff --git a/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.kt b/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.kt
new file mode 100644
index 0000000..7e2a1e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.view.Surface
+import android.view.Surface.Rotation
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import java.io.PrintWriter
+
+class StatusBarInsetsCommand(
+    private val callback: Callback,
+) : ParseableCommand(NAME) {
+
+    val bottomMargin: BottomMarginCommand? by subCommand(BottomMarginCommand())
+
+    override fun execute(pw: PrintWriter) {
+        callback.onExecute(command = this, pw)
+    }
+
+    interface Callback {
+        fun onExecute(command: StatusBarInsetsCommand, printWriter: PrintWriter)
+    }
+
+    companion object {
+        const val NAME = "status-bar-insets"
+    }
+}
+
+class BottomMarginCommand : ParseableCommand(NAME) {
+
+    private val rotationDegrees: Int? by
+        param(
+            longName = "rotation",
+            shortName = "r",
+            description = "For which rotation the margin should be set. One of 0, 90, 180, 270",
+            valueParser = Type.Int,
+        )
+
+    @Rotation
+    val rotationValue: Int?
+        get() = ROTATION_DEGREES_TO_VALUE_MAPPING[rotationDegrees]
+
+    val marginBottomDp: Float? by
+        param(
+            longName = "margin",
+            shortName = "m",
+            description = "Margin amount, in dp. Can be a fractional value, such as 10.5",
+            valueParser = Type.Float,
+        )
+
+    override fun execute(pw: PrintWriter) {
+        // Not needed for a subcommand
+    }
+
+    companion object {
+        const val NAME = "bottom-margin"
+        private val ROTATION_DEGREES_TO_VALUE_MAPPING =
+            mapOf(
+                0 to Surface.ROTATION_0,
+                90 to Surface.ROTATION_90,
+                180 to Surface.ROTATION_180,
+                270 to Surface.ROTATION_270,
+            )
+
+        val ROTATION_DEGREES_OPTIONS: Set<Int> = ROTATION_DEGREES_TO_VALUE_MAPPING.keys
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
index 4944531..ba943b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
@@ -22,8 +22,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -36,7 +36,7 @@
 
     private static final String TAG = "WindowMagnificationConnectionImpl";
 
-    private IWindowMagnificationConnectionCallback mConnectionCallback;
+    private IMagnificationConnectionCallback mConnectionCallback;
     private final Magnification mMagnification;
     private final Handler mHandler;
 
@@ -105,7 +105,7 @@
     }
 
     @Override
-    public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) {
+    public void setConnectionCallback(IMagnificationConnectionCallback callback) {
         mConnectionCallback = callback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index a2ac66f..63fe26a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -197,11 +197,42 @@
                 listenForGoneToAodTransition(this)
                 listenForLockscreenAodTransitions(this)
                 listenForAodToOccludedTransitions(this)
+                listenForAlternateBouncerToAodTransitions(this)
+                listenForDreamingToAodTransitions(this)
             }
         }
     }
 
     @VisibleForTesting
+    suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
+                transitionStep ->
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    ANIMATE_APPEAR_ON_SCREEN_OFF,
+                )
+            }
+        }
+    }
+
+    @VisibleForTesting
+    suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor
+                .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
+                .collect { transitionStep ->
+                    view.onDozeAmountChanged(
+                        transitionStep.value,
+                        transitionStep.value,
+                        UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                    )
+                }
+        }
+    }
+
+    @VisibleForTesting
     suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
@@ -246,7 +277,10 @@
     suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.dozeAmountTransition.collect { transitionStep ->
-                if (transitionStep.transitionState == TransitionState.CANCELED) {
+                if (
+                    transitionStep.from == KeyguardState.AOD &&
+                        transitionStep.transitionState == TransitionState.CANCELED
+                ) {
                     if (
                         transitionInteractor.startedKeyguardTransitionStep.first().to !=
                             KeyguardState.AOD
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
index 5385442..7f97718 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -21,13 +21,13 @@
 /** Enumerates all known adaptive layout configurations. */
 enum class BouncerSceneLayout {
     /** The default UI with the bouncer laid out normally. */
-    STANDARD,
+    STANDARD_BOUNCER,
     /** The bouncer is displayed vertically stacked with the user switcher. */
-    STACKED,
+    BELOW_USER_SWITCHER,
     /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
-    SIDE_BY_SIDE,
+    BESIDE_USER_SWITCHER,
     /** The bouncer is split in two with both sides shown side-by-side. */
-    SPLIT,
+    SPLIT_BOUNCER,
 }
 
 /** Enumerates the supported window size classes. */
@@ -48,19 +48,19 @@
     isSideBySideSupported: Boolean,
 ): BouncerSceneLayout {
     return when (height) {
-        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
         SizeClass.MEDIUM ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
         SizeClass.EXPANDED ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
-    }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
-        ?: BouncerSceneLayout.STANDARD
+    }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported }
+        ?: BouncerSceneLayout.STANDARD_BOUNCER
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 3119b9e..1f4be40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -18,18 +18,26 @@
 
 import com.android.systemui.Flags.communalHub
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
@@ -45,14 +53,26 @@
      */
     val desiredScene: StateFlow<CommunalSceneKey>
 
+    /** Exposes the transition state of the communal [SceneTransitionLayout]. */
+    val transitionState: StateFlow<ObservableCommunalTransitionState>
+
     /** Updates the requested scene. */
     fun setDesiredScene(desiredScene: CommunalSceneKey)
+
+    /**
+     * Updates the transition state of the hub [SceneTransitionLayout].
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalRepositoryImpl
 @Inject
 constructor(
+    @Background backgroundScope: CoroutineScope,
     private val featureFlagsClassic: FeatureFlagsClassic,
     sceneContainerFlags: SceneContainerFlags,
     sceneContainerRepository: SceneContainerRepository,
@@ -61,13 +81,34 @@
         get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
 
     private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
-        MutableStateFlow(CommunalSceneKey.Blank)
+        MutableStateFlow(CommunalSceneKey.DEFAULT)
     override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
 
+    private val defaultTransitionState =
+        ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)
+    private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null)
+    override val transitionState: StateFlow<ObservableCommunalTransitionState> =
+        _transitionState
+            .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
+            .stateIn(
+                scope = backgroundScope,
+                started = SharingStarted.Lazily,
+                initialValue = defaultTransitionState,
+            )
+
     override fun setDesiredScene(desiredScene: CommunalSceneKey) {
         _desiredScene.value = desiredScene
     }
 
+    /**
+     * Updates the transition state of the hub [SceneTransitionLayout].
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+        _transitionState.value = transitionState
+    }
+
     override val isCommunalHubShowing: Flow<Boolean> =
         if (sceneContainerFlags.isEnabled()) {
             sceneContainerRepository.desiredScene.map { scene -> scene.key == SceneKey.Communal }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index e630fd4..e342c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -62,6 +63,19 @@
      */
     val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene
 
+    /** Transition state of the hub mode. */
+    val transitionState: StateFlow<ObservableCommunalTransitionState> =
+        communalRepository.transitionState
+
+    /**
+     * Updates the transition state of the hub [SceneTransitionLayout].
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+        communalRepository.setTransitionState(transitionState)
+    }
+
     /**
      * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
      * [CommunalSceneKey.Communal].
@@ -108,7 +122,7 @@
         if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
             flowOf(emptyList())
         } else {
-            smartspaceRepository.lockscreenSmartspaceTargets.map { targets ->
+            smartspaceRepository.communalSmartspaceTargets.map { targets ->
                 targets
                     .filter { target ->
                         target.featureType == SmartspaceTarget.FEATURE_TIMER &&
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
index 2be909c..c68dd4f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
@@ -29,4 +29,8 @@
     override fun toString(): String {
         return loggingName
     }
+
+    companion object {
+        val DEFAULT = Blank
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt
new file mode 100644
index 0000000..d834715
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * This is a fork of the `com.android.compose.animation.scene.ObservableTransitionState` class.
+ *
+ * TODO(b/315490861): remove this fork, once we can compile Compose into System UI.
+ */
+sealed class ObservableCommunalTransitionState {
+    /** No transition/animation is currently running. */
+    data class Idle(val scene: CommunalSceneKey) : ObservableCommunalTransitionState()
+
+    /** There is a transition animating between two scenes. */
+    data class Transition(
+        val fromScene: CommunalSceneKey,
+        val toScene: CommunalSceneKey,
+        val progress: Flow<Float>,
+
+        /**
+         * Whether the transition was originally triggered by user input rather than being
+         * programmatic. If this value is initially true, it will remain true until the transition
+         * fully completes, even if the user input that triggered the transition has ended. Any
+         * sub-transitions launched by this one will inherit this value. For example, if the user
+         * drags a pointer but does not exceed the threshold required to transition to another
+         * scene, this value will remain true after the pointer is no longer touching the screen and
+         * will be true in any transition created to animate back to the original position.
+         */
+        val isInitiatedByUserInput: Boolean,
+
+        /**
+         * Whether user input is currently driving the transition. For example, if a user is
+         * dragging a pointer, this emits true. Once they lift their finger, this emits false while
+         * the transition completes/settles.
+         */
+        val isUserInputOngoing: Flow<Boolean>,
+    ) : ObservableCommunalTransitionState()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
new file mode 100644
index 0000000..c5610c87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.smartspace
+
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
+import android.content.Context
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.smartspace.SmartspacePrecondition
+import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.util.concurrency.Execution
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for managing the smartspace view on the dream */
+@SysUISingleton
+class CommunalSmartspaceController
+@Inject
+constructor(
+    private val context: Context,
+    private val smartspaceManager: SmartspaceManager?,
+    private val execution: Execution,
+    @Main private val uiExecutor: Executor,
+    @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
+    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
+    private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
+    @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+) {
+    companion object {
+        private const val TAG = "CommunalSmartspaceCtrlr"
+    }
+
+    private var session: SmartspaceSession? = null
+    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
+    private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
+
+    // A shadow copy of listeners is maintained to track whether the session should remain open.
+    private var listeners = mutableSetOf<SmartspaceTargetListener>()
+
+    private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
+
+    // Smartspace can be used on multiple displays, such as when the user casts their screen
+    private var smartspaceViews = mutableSetOf<SmartspaceView>()
+
+    var preconditionListener =
+        object : SmartspacePrecondition.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
+        }
+
+    init {
+        precondition.addListener(preconditionListener)
+    }
+
+    var filterListener =
+        object : SmartspaceTargetFilter.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
+        }
+
+    init {
+        targetFilter?.addListener(filterListener)
+    }
+
+    private val sessionListener =
+        SmartspaceSession.OnTargetsAvailableListener { targets ->
+            execution.assertIsMainThread()
+
+            val filteredTargets =
+                targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+            plugin?.onTargetsAvailable(filteredTargets)
+        }
+
+    private fun hasActiveSessionListeners(): Boolean {
+        return smartspaceViews.isNotEmpty() ||
+            listeners.isNotEmpty() ||
+            unfilteredListeners.isNotEmpty()
+    }
+
+    private fun connectSession() {
+        if (smartspaceManager == null) {
+            return
+        }
+        if (plugin == null) {
+            return
+        }
+        if (session != null || !hasActiveSessionListeners()) {
+            return
+        }
+
+        if (!precondition.conditionsMet()) {
+            return
+        }
+
+        val newSession =
+            smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+            )
+        Log.d(TAG, "Starting smartspace session for dream")
+        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        this.session = newSession
+
+        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+
+        reloadSmartspace()
+    }
+
+    /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
+    private fun disconnect() {
+        if (hasActiveSessionListeners()) return
+
+        execution.assertIsMainThread()
+
+        if (session == null) {
+            return
+        }
+
+        session?.let {
+            it.removeOnTargetsAvailableListener(sessionListener)
+            it.close()
+        }
+
+        session = null
+
+        plugin?.registerSmartspaceEventNotifier(null)
+        plugin?.onTargetsAvailable(emptyList())
+        Log.d(TAG, "Ending smartspace session for dream")
+    }
+
+    fun addListener(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, plugin)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, plugin)
+    }
+
+    private fun addAndRegisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
+        execution.assertIsMainThread()
+        smartspaceDataPlugin?.registerListener(listener)
+        listeners.add(listener)
+
+        connectSession()
+    }
+
+    private fun removeAndUnregisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
+        execution.assertIsMainThread()
+        smartspaceDataPlugin?.unregisterListener(listener)
+        listeners.remove(listener)
+        disconnect()
+    }
+
+    private fun reloadSmartspace() {
+        session?.requestSmartspaceUpdate()
+    }
+
+    private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
+        unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 333fc19..708f137 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.shade.ShadeViewController
 import javax.inject.Provider
@@ -43,6 +44,15 @@
         communalInteractor.onSceneChanged(scene)
     }
 
+    /**
+     * Updates the transition state of the hub [SceneTransitionLayout].
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+        communalInteractor.setTransitionState(transitionState)
+    }
+
     // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
     //  touches anymore.
     /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
index 63b01ed..0daa058 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
@@ -35,23 +35,22 @@
 import javax.inject.Inject
 
 /** Dialog to select contrast options */
-class ContrastDialogDelegate @Inject constructor(
-    private val sysuiDialogFactory : SystemUIDialog.Factory,
+class ContrastDialogDelegate
+@Inject
+constructor(
+    private val sysuiDialogFactory: SystemUIDialog.Factory,
     @Main private val mainExecutor: Executor,
     private val uiModeManager: UiModeManager,
     private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
 ) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
 
-    override fun createDialog(): SystemUIDialog {
-        return sysuiDialogFactory.create(this)
-    }
-
     @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
     lateinit var dialogView: View
     @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
 
-    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+    override fun createDialog(): SystemUIDialog {
+        val dialog = sysuiDialogFactory.create(this)
         dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
         with(dialog) {
             setView(dialogView)
@@ -67,12 +66,16 @@
             }
             setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
         }
+
+        return dialog
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         contrastButtons =
             mapOf(
-                CONTRAST_LEVEL_STANDARD to dialogView.requireViewById(
-                    R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to dialogView.requireViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to dialogView.requireViewById(R.id.contrast_button_high)
+                CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
+                CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
+                CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
             )
 
         contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 9c13a8c..3fac865 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -43,7 +43,7 @@
         if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
             int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
-                mKeyguardUpdateMonitor.clearBiometricRecognized();
+                mKeyguardUpdateMonitor.clearFingerprintRecognized();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index d5b95d67..5ec51f4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -16,6 +16,12 @@
 
 package com.android.systemui.flags
 
+import com.android.server.notification.Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS
+import com.android.server.notification.Flags.FLAG_POLITE_NOTIFICATIONS
+import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED
+import com.android.server.notification.Flags.crossAppPoliteNotifications
+import com.android.server.notification.Flags.politeNotifications
+import com.android.server.notification.Flags.vibrateWhileUnlocked
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.SysUISingleton
@@ -36,5 +42,14 @@
         val keyguardBottomAreaRefactor = FlagToken(
                 FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
         KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
+
+        val crossAppPoliteNotifToken =
+                FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+        val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+        crossAppPoliteNotifToken dependsOn politeNotifToken
+
+        val vibrateWhileUnlockedToken =
+                FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+        vibrateWhileUnlockedToken dependsOn politeNotifToken
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 5a763b1..b1d4587 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -111,7 +111,7 @@
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
-            unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true)
+            releasedFlag("notification_lockscreen_mgr_bg_thread")
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -437,11 +437,6 @@
     // 1200 - predictive back
     @Keep
     @JvmField
-    val WM_ENABLE_PREDICTIVE_BACK =
-        sysPropBooleanFlag("persist.wm.debug.predictive_back", default = true)
-
-    @Keep
-    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
         sysPropBooleanFlag("persist.wm.debug.predictive_back_anim", default = true)
 
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 7b33e11..6cb68ba 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -42,4 +42,6 @@
     @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
     /** Vibration scale at the lower bookend of the slider */
     @FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
+    /** Exponent for power function compensation */
+    @FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index f313fb3..9e6245a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -21,9 +21,11 @@
 import android.view.VelocityTracker
 import android.view.animation.AccelerateInterpolator
 import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.VibratorHelper
 import kotlin.math.abs
 import kotlin.math.min
+import kotlin.math.pow
 
 /**
  * Listener of slider events that triggers haptic feedback.
@@ -63,18 +65,29 @@
      * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
      */
     private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
+        val powerScale = scaleOnEdgeCollision(absoluteVelocity)
+        val vibration =
+            VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
+                .compose()
+        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+    }
+
+    /**
+     * Get the velocity-based scale at the bookends
+     *
+     * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
+     * @return The power scale for the vibration.
+     */
+    @VisibleForTesting
+    fun scaleOnEdgeCollision(absoluteVelocity: Float): Float {
         val velocityInterpolated =
             velocityAccelerateInterpolator.getInterpolation(
                 min(absoluteVelocity / config.maxVelocityToScale, 1f)
             )
         val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale
         val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale
-
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, bookendsHitScale)
-                .compose()
-        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+        return bookendsHitScale.pow(config.exponent)
     }
 
     /**
@@ -96,6 +109,31 @@
         val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
         if (deltaProgress < config.deltaProgressForDragThreshold) return
 
+        val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
+
+        // Trigger the vibration composition
+        val composition = VibrationEffect.startComposition()
+        repeat(config.numberOfLowTicks) {
+            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
+        }
+        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+        dragTextureLastTime = currentTime
+        dragTextureLastProgress = normalizedSliderProgress
+    }
+
+    /**
+     * Get the scale of the drag texture vibration.
+     *
+     * @param[absoluteVelocity] Absolute velocity of the handle.
+     * @param[normalizedSliderProgress] Progress of the slider handled normalized to the range from
+     *   0F to 1F (inclusive).
+     *     @return the scale of the vibration.
+     */
+    @VisibleForTesting
+    fun scaleOnDragTexture(
+        absoluteVelocity: Float,
+        @FloatRange(from = 0.0, to = 1.0) normalizedSliderProgress: Float
+    ): Float {
         val velocityInterpolated =
             velocityAccelerateInterpolator.getInterpolation(
                 min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -113,15 +151,7 @@
 
         // Total scale
         val scale = positionBasedScale + velocityBasedScale
-
-        // Trigger the vibration composition
-        val composition = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
-        }
-        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
-        dragTextureLastTime = currentTime
-        dragTextureLastProgress = normalizedSliderProgress
+        return scale.pow(config.exponent)
     }
 
     override fun onHandleAcquiredByTouch() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3009087..b7260f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2611,14 +2611,14 @@
         }
 
         if (mGoingToSleep) {
-            mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+            mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
             Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
             return;
         }
         setPendingLock(false); // user may have authenticated during the screen off animation
 
         handleHide();
-        mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+        mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index e47c448..eceaf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -326,7 +326,7 @@
                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 },
             )
-            .flowOn(backgroundDispatcher)
+            .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
                     clearPendingAuthRequest("Resetting auth status")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index b51edab6..0df7f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -160,6 +160,9 @@
     /** Last point that [KeyguardRootView] was tapped */
     val lastRootViewTapPosition: MutableStateFlow<Point?>
 
+    /** Is the ambient indication area visible? */
+    val ambientIndicationVisible: MutableStateFlow<Boolean>
+
     /** Observable for the [StatusBarState] */
     val statusBarState: StateFlow<StatusBarState>
 
@@ -423,6 +426,8 @@
 
     override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
 
+    override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     override val isDreamingWithOverlay: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0b6b971..7fdcf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -48,7 +48,7 @@
     override fun start() {
         listenForDreamingToOccluded()
         listenForDreamingToGone()
-        listenForDreamingToDozing()
+        listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
@@ -94,7 +94,7 @@
         }
     }
 
-    private fun listenForDreamingToDozing() {
+    private fun listenForDreamingToAodOrDozing() {
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
@@ -102,11 +102,12 @@
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
-                    if (
-                        dozeTransitionModel.to == DozeStateModel.DOZE &&
-                            keyguardState == KeyguardState.DREAMING
-                    ) {
-                        startTransitionTo(KeyguardState.DOZING)
+                    if (keyguardState == KeyguardState.DREAMING) {
+                        if (dozeTransitionModel.to == DozeStateModel.DOZE) {
+                            startTransitionTo(KeyguardState.DOZING)
+                        } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) {
+                            startTransitionTo(KeyguardState.AOD)
+                        }
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 5ed70b5..046916a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -78,6 +78,9 @@
  * flows.
  */
 interface FaceAuthenticationListener {
+    /** Receive face isAuthenticated updates */
+    fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
     /** Receive face authentication status updates */
     fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c12efe8..defca18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -174,6 +174,9 @@
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
 
+    /** Is the ambient indication area visible? */
+    val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow()
+
     /** Whether the primary bouncer is showing or not. */
     val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
 
@@ -311,6 +314,10 @@
         repository.lastRootViewTapPosition.value = point
     }
 
+    fun setAmbientIndicationVisible(isVisible: Boolean) {
+        repository.ambientIndicationVisible.value = isVisible
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8dde399..7882a97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -120,10 +120,14 @@
      * This is useful for experiences like the lock screen preview mode, where the affordances must
      * always be visible.
      */
-    fun quickAffordanceAlwaysVisible(
+    suspend fun quickAffordanceAlwaysVisible(
         position: KeyguardQuickAffordancePosition,
     ): Flow<KeyguardQuickAffordanceModel> {
-        return quickAffordanceInternal(position)
+        return if (isFeatureDisabledByDevicePolicy()) {
+            flowOf(KeyguardQuickAffordanceModel.Hidden)
+        } else {
+            quickAffordanceInternal(position)
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 532df4a..fb20000 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.Context
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceWakeUpTriggersConfig
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -83,6 +85,7 @@
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val trustManager: TrustManager,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -291,6 +294,20 @@
             .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } }
             .flowOn(mainDispatcher)
             .launchIn(applicationScope)
+        repository.isAuthenticated
+            .sample(userRepository.selectedUserInfo, ::Pair)
+            .onEach { (isAuthenticated, userInfo) ->
+                if (!isAuthenticated) {
+                    faceAuthenticationLogger.clearFaceRecognized()
+                    trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .onEach { (isAuthenticated, _) ->
+                listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
 
         biometricSettingsRepository.isFaceAuthEnrolledAndEnabled
             .onEach { enrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1d4520f..26dace0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -180,7 +180,9 @@
                     goneToAodTransitionViewModel
                         .enterFromTopTranslationY(enterFromTopAmount)
                         .onStart { emit(0f) },
-                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
+                        emit(0f)
+                    },
                 ) {
                     keyguardTransitionY,
                     burnInTranslationY,
@@ -193,6 +195,7 @@
                         occludedToLockscreenTransitionTranslationY
                 }
             }
+            .distinctUntilChanged()
 
     val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() }
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 8c5690b..3c2facb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -132,6 +132,10 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
+    fun clearFaceRecognized() {
+        logBuffer.log(TAG, DEBUG, "Clear face recognized")
+    }
+
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 9cdf857..992eeca 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -40,6 +40,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -76,6 +77,7 @@
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TreeMap
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -102,6 +104,7 @@
     private val activityStarter: ActivityStarter,
     private val systemClock: SystemClock,
     @Main executor: DelayableExecutor,
+    @Background private val bgExecutor: Executor,
     private val mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager,
@@ -1030,7 +1033,7 @@
             desiredHostState?.let {
                 if (this.desiredLocation != desiredLocation) {
                     // Only log an event when location changes
-                    logger.logCarouselPosition(desiredLocation)
+                    bgExecutor.execute { logger.logCarouselPosition(desiredLocation) }
                 }
 
                 // This is a hosting view, let's remeasure our players
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 8d1ff98a..62c7343 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -31,7 +31,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -322,7 +321,6 @@
                 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
                 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
                         allowSystemGestureIgnoringBarVisibility())
-                .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
                 .commitUpdate(mDisplayId);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index fdc70a8..76ef8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -278,7 +278,12 @@
                     d.setShowForAllUsers(true)
                     d.addOnDismissListener(onDialogDismissed)
                     if (view != null) {
-                        dialogLaunchAnimator.showFromView(d, view)
+                        val controller = getPrivacyDialogController(view)
+                        if (controller == null) {
+                            d.show()
+                        } else {
+                            dialogLaunchAnimator.show(d, controller)
+                        }
                     } else {
                         d.show()
                     }
@@ -291,6 +296,13 @@
         }
     }
 
+    private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
+        val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+        return object : DialogLaunchAnimator.Controller by delegate {
+            override fun shouldAnimateExit() = false
+        }
+    }
+
     /** Dismisses the dialog */
     fun dismissDialog() {
         dialog?.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/OWNERS b/packages/SystemUI/src/com/android/systemui/qs/OWNERS
new file mode 100644
index 0000000..45a4b50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/OWNERS
@@ -0,0 +1,10 @@
+set noparent
+
+# Bug component: 78010
+
+apotapov@google.com
+asc@google.com
+bhnm@google.com
+kozynski@google.com
+ostonge@google.com
+pixel@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index b50798e..4bad45f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
@@ -39,14 +40,17 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.Multibinds
 
-@Module(includes = [QSAutoAddModule::class])
+@Module(includes = [QSAutoAddModule::class, RestoreProcessorsModule::class])
 abstract class QSPipelineModule {
 
     /** Implementation for [TileSpecRepository] */
     @Binds
     abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
 
+    @Multibinds abstract fun provideRestoreProcessors(): Set<RestoreProcessor>
+
     @Binds
     abstract fun provideDefaultTilesRepository(
         impl: DefaultTilesQSHostRepository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt
new file mode 100644
index 0000000..e970c84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface RestoreProcessorsModule {
+
+    @Binds
+    @IntoSet
+    fun bindWorkTileRestoreProcessor(impl: WorkTileRestoreProcessor): RestoreProcessor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt
new file mode 100644
index 0000000..8f7de19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.model
+
+/**
+ * Perform processing of the [RestoreData] before or after it's applied to repositories.
+ *
+ * The order in which the restore processors are applied in not deterministic.
+ *
+ * In order to declare a restore processor, add it in [RestoreProcessingModule] using
+ *
+ * ```
+ * @Binds
+ * @IntoSet
+ * ``
+ */
+interface RestoreProcessor {
+
+    /** Should be called before applying the restore to the necessary repositories */
+    suspend fun preProcessRestore(restoreData: RestoreData) {}
+
+    /** Should be called after requesting the repositories to update. */
+    suspend fun postProcessRestore(restoreData: RestoreData) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
index 7998dfb..d40f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
@@ -20,9 +20,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import kotlinx.coroutines.flow.Flow
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 /** Repository to track what QS tiles have been auto-added */
 interface AutoAddRepository {
@@ -49,8 +48,9 @@
 @SysUISingleton
 class AutoAddSettingRepository
 @Inject
-constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) :
-    AutoAddRepository {
+constructor(
+    private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory,
+) : AutoAddRepository {
 
     private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>()
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
index 6cee116..e718eea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
@@ -10,6 +10,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY
 import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import javax.inject.Inject
@@ -28,6 +29,14 @@
 /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */
 interface QSSettingsRestoredRepository {
     val restoreData: Flow<RestoreData>
+
+    companion object {
+        // This capacity is the number of restore data that we will keep buffered in the shared
+        // flow. It is unlikely that at any given time there would be this many restores being
+        // processed by consumers, but just in case that a couple of users are restored at the
+        // same time and they need to be replayed for the consumers of the flow.
+        const val BUFFER_CAPACITY = 10
+    }
 }
 
 @SysUISingleton
@@ -86,11 +95,6 @@
 
     private companion object {
         private const val TAG = "QSSettingsRestoredBroadcastRepository"
-        // This capacity is the number of restore data that we will keep buffered in the shared
-        // flow. It is unlikely that at any given time there would be this many restores being
-        // processed by consumers, but just in case that a couple of users are restored at the
-        // same time and they need to be replayed for the consumers of the flow.
-        private const val BUFFER_CAPACITY = 10
 
         private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
         private const val TILES_SETTING = Settings.Secure.QS_TILES
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt
new file mode 100644
index 0000000..7376aa9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.restoreprocessors
+
+import android.os.UserHandle
+import android.util.SparseIntArray
+import androidx.annotation.GuardedBy
+import androidx.core.util.getOrDefault
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+/**
+ * Processor for restore data for work tile.
+ *
+ * It will indicate when auto-add tracking may be removed for a user. This may be necessary if the
+ * tile will be destroyed due to being not available, but needs to be added once work profile is
+ * enabled (after restore), in the same position as it was in the restored data.
+ */
+@SysUISingleton
+class WorkTileRestoreProcessor @Inject constructor() : RestoreProcessor {
+
+    @GuardedBy("lastRestorePosition") private val lastRestorePosition = SparseIntArray()
+
+    private val _removeTrackingForUser =
+        MutableSharedFlow<Int>(extraBufferCapacity = QSSettingsRestoredRepository.BUFFER_CAPACITY)
+
+    /**
+     * Flow indicating that we may need to remove auto-add tracking for the work tile for a given
+     * user.
+     */
+    fun removeTrackingForUser(userHandle: UserHandle): Flow<Unit> {
+        return _removeTrackingForUser.filter { it == userHandle.identifier }.map {}
+    }
+
+    override suspend fun postProcessRestore(restoreData: RestoreData) {
+        if (TILE_SPEC in restoreData.restoredTiles) {
+            synchronized(lastRestorePosition) {
+                lastRestorePosition.put(
+                    restoreData.userId,
+                    restoreData.restoredTiles.indexOf(TILE_SPEC)
+                )
+            }
+            _removeTrackingForUser.emit(restoreData.userId)
+        }
+    }
+
+    fun pollLastPosition(userId: Int): Int {
+        return synchronized(lastRestorePosition) {
+            lastRestorePosition.getOrDefault(userId, TileSpecRepository.POSITION_AT_END).also {
+                lastRestorePosition.delete(userId)
+            }
+        }
+    }
+
+    companion object {
+        private val TILE_SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
index 5e3c348..b221199 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.content.pm.UserInfo
+import android.os.UserHandle
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
 import com.android.systemui.qs.pipeline.domain.model.AutoAddable
@@ -28,6 +30,8 @@
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
 
 /**
  * [AutoAddable] for [WorkModeTile.TILE_SPEC].
@@ -36,17 +40,37 @@
  * signal to remove it if there is not.
  */
 @SysUISingleton
-class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+class WorkTileAutoAddable
+@Inject
+constructor(
+    private val userTracker: UserTracker,
+    private val workTileRestoreProcessor: WorkTileRestoreProcessor,
+) : AutoAddable {
 
     private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
 
     override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
-        return conflatedCallbackFlow {
+        val removeTrackingDueToRestore: Flow<AutoAddSignal> =
+            workTileRestoreProcessor.removeTrackingForUser(UserHandle.of(userId)).mapNotNull {
+                val profiles = userTracker.userProfiles
+                if (profiles.any { it.id == userId } && !profiles.any { it.isManagedProfile }) {
+                    // Only remove auto-added if there are no managed profiles for this user
+                    AutoAddSignal.RemoveTracking(spec)
+                } else {
+                    null
+                }
+            }
+        val signalsFromCallback = conflatedCallbackFlow {
             fun maybeSend(profiles: List<UserInfo>) {
                 if (profiles.any { it.id == userId }) {
                     // We are looking at the profiles of the correct user.
                     if (profiles.any { it.isManagedProfile }) {
-                        trySend(AutoAddSignal.Add(spec))
+                        trySend(
+                            AutoAddSignal.Add(
+                                spec,
+                                workTileRestoreProcessor.pollLastPosition(userId),
+                            )
+                        )
                     } else {
                         trySend(AutoAddSignal.Remove(spec))
                     }
@@ -65,6 +89,7 @@
 
             awaitClose { userTracker.removeCallback(callback) }
         }
+        return merge(removeTrackingDueToRestore, signalsFromCallback)
     }
 
     override val autoAddTracking = AutoAddTracking.Always
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
index b747393..cde3835 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -103,6 +103,10 @@
                                     qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
                                     repository.unmarkTileAdded(userId, signal.spec)
                                 }
+                                is AutoAddSignal.RemoveTracking -> {
+                                    qsPipelineLogger.logTileUnmarked(userId, signal.spec)
+                                    repository.unmarkTileAdded(userId, signal.spec)
+                                }
                             }
                         }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
index 9844903..a5be14e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
@@ -3,14 +3,15 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flatMapConcat
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.take
@@ -33,6 +34,8 @@
     private val tileSpecRepository: TileSpecRepository,
     private val autoAddRepository: AutoAddRepository,
     private val qsSettingsRestoredRepository: QSSettingsRestoredRepository,
+    private val restoreProcessors: Set<@JvmSuppressWildcards RestoreProcessor>,
+    private val qsPipelineLogger: QSPipelineLogger,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
@@ -40,14 +43,30 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     fun start() {
         applicationScope.launch(backgroundDispatcher) {
-            qsSettingsRestoredRepository.restoreData.flatMapConcat { data ->
-                autoAddRepository.autoAddedTiles(data.userId)
-                        .take(1)
-                        .map { tiles -> data to tiles }
-            }.collect { (restoreData, autoAdded) ->
-                tileSpecRepository.reconcileRestore(restoreData, autoAdded)
-                autoAddRepository.reconcileRestore(restoreData)
-            }
+            qsSettingsRestoredRepository.restoreData
+                .flatMapConcat { data ->
+                    autoAddRepository.autoAddedTiles(data.userId).take(1).map { tiles ->
+                        data to tiles
+                    }
+                }
+                .collect { (restoreData, autoAdded) ->
+                    restoreProcessors.forEach {
+                        it.preProcessRestore(restoreData)
+                        qsPipelineLogger.logRestoreProcessorApplied(
+                            it::class.simpleName,
+                            QSPipelineLogger.RestorePreprocessorStep.PREPROCESSING,
+                        )
+                    }
+                    tileSpecRepository.reconcileRestore(restoreData, autoAdded)
+                    autoAddRepository.reconcileRestore(restoreData)
+                    restoreProcessors.forEach {
+                        it.postProcessRestore(restoreData)
+                        qsPipelineLogger.logRestoreProcessorApplied(
+                            it::class.simpleName,
+                            QSPipelineLogger.RestorePreprocessorStep.POSTPROCESSING,
+                        )
+                    }
+                }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
index ed7b8bd..8263680 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -34,4 +34,9 @@
     data class Remove(
         override val spec: TileSpec,
     ) : AutoAddSignal
+
+    /** Signal for remove the auto-add marker from the tile, but not remove the tile */
+    data class RemoveTracking(
+        override val spec: TileSpec,
+    ) : AutoAddSignal
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index bca86c9..7d2c6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -209,6 +209,18 @@
         )
     }
 
+    fun logTileUnmarked(userId: Int, spec: TileSpec) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                str1 = spec.toString()
+            },
+            { "Tile $str1 unmarked as auto-added for user $int1" }
+        )
+    }
+
     fun logSettingsRestored(restoreData: RestoreData) {
         restoreLogBuffer.log(
             RESTORE_TAG,
@@ -226,6 +238,21 @@
         )
     }
 
+    fun logRestoreProcessorApplied(
+        restoreProcessorClassName: String?,
+        step: RestorePreprocessorStep,
+    ) {
+        restoreLogBuffer.log(
+            RESTORE_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = restoreProcessorClassName
+                str2 = step.name
+            },
+            { "Restore $str2 processed by $str1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
@@ -234,4 +261,9 @@
         EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"),
         TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"),
     }
+
+    enum class RestorePreprocessorStep {
+        PREPROCESSING,
+        POSTPROCESSING
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 09d7a1f..17b78eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -24,7 +24,7 @@
      * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to
      * [QSTileDataInteractor] to get [QSTileInput.data].
      *
-     * It's safe to run long running computations inside this function in this.
+     * It's safe to run long running computations inside this function.
      */
     @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index 4a34276..b325b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.di
 
+import android.content.Context
+import android.content.res.Resources.Theme
 import com.android.systemui.qs.external.CustomTileStatePersister
 import com.android.systemui.qs.external.CustomTileStatePersisterImpl
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
@@ -27,6 +29,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.Multibinds
 
 /** Module listing subcomponents */
@@ -57,4 +60,9 @@
 
     @Binds
     fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister
+
+    companion object {
+
+        @Provides fun provideTilesTheme(context: Context): Theme = context.theme
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 2350b5d..9d214e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -59,12 +59,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wifitrackerlib.WifiEntry;
@@ -157,14 +157,6 @@
 
     // Wi-Fi scanning progress bar
     protected boolean mIsProgressBarVisible;
-    protected boolean mIsSearchingHidden;
-    protected final Runnable mHideProgressBarRunnable = () -> {
-        setProgressBarVisible(false);
-    };
-    protected Runnable mHideSearchingRunnable = () -> {
-        mIsSearchingHidden = true;
-        mInternetDialogSubTitle.setText(getSubtitleText());
-    };
 
     @Inject
     public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
@@ -285,8 +277,6 @@
         if (DEBUG) {
             Log.d(TAG, "onStop");
         }
-        mHandler.removeCallbacks(mHideProgressBarRunnable);
-        mHandler.removeCallbacks(mHideSearchingRunnable);
         mMobileNetworkLayout.setOnClickListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
@@ -335,7 +325,6 @@
             return;
         }
 
-        showProgressBar();
         final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
         final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
         final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
@@ -641,8 +630,7 @@
 
     @Nullable
     CharSequence getSubtitleText() {
-        return mInternetDialogController.getSubtitleText(
-                mIsProgressBarVisible && !mIsSearchingHidden);
+        return mInternetDialogController.getSubtitleText(mIsProgressBarVisible);
     }
 
     private Drawable getSignalStrengthDrawable(int subId) {
@@ -657,20 +645,6 @@
         return mInternetDialogController.getMobileNetworkSummary(subId);
     }
 
-    protected void showProgressBar() {
-        if (!mInternetDialogController.isWifiEnabled()
-                || mInternetDialogController.isDeviceLocked()) {
-            setProgressBarVisible(false);
-            return;
-        }
-        setProgressBarVisible(true);
-        if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) {
-            mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS);
-        } else if (!mIsSearchingHidden) {
-            mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS);
-        }
-    }
-
     private void setProgressBarVisible(boolean visible) {
         if (mIsProgressBarVisible == visible) {
             return;
@@ -823,6 +797,11 @@
     }
 
     @Override
+    public void onWifiScan(boolean isScan) {
+        setProgressBarVisible(isScan);
+    }
+
+    @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
         if (mAlertDialog != null && !mAlertDialog.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index f516f55..592cb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -75,7 +75,6 @@
 import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.settingslib.wifi.dpp.WifiDppIntentHelper;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -84,6 +83,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -1129,6 +1129,15 @@
     public void onSettingsActivityTriggered(Intent settingsIntent) {
     }
 
+    @Override
+    public void onWifiScan(boolean isScan) {
+        if (!isWifiEnabled() || isDeviceLocked()) {
+            mCallback.onWifiScan(false);
+            return;
+        }
+        mCallback.onWifiScan(isScan);
+    }
+
     private class InternetTelephonyCallback extends TelephonyCallback implements
             TelephonyCallback.DataConnectionStateListener,
             TelephonyCallback.DisplayInfoListener,
@@ -1372,6 +1381,8 @@
 
         void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
                 @Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries);
+
+        void onWifiScan(boolean isScan);
     }
 
     void makeOverlayToast(int stringId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index cfb5442..9b8dba1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.airplane.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
-class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<AirplaneModeTileModel> {
+class AirplaneModeMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    val theme: Theme,
+) : QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_airplane_icon_on
-                    } else {
-                        R.drawable.qs_airplane_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_airplane_icon_on
+                        } else {
+                            R.drawable.qs_airplane_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index 6386577..e075e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.alarm.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -30,14 +31,18 @@
 import javax.inject.Inject
 
 /** Maps [AlarmTileModel] to [QSTileState]. */
-class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<AlarmTileModel> {
+class AlarmTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<AlarmTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
     }
     override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             when (data) {
                 is AlarmTileModel.NextAlarmSet -> {
                     activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 881a6bd..1b3b584 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [FlashlightTileModel] to [QSTileState]. */
-class FlashlightMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<FlashlightTileModel> {
+class FlashlightMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_flashlight_icon_on
-                    } else {
-                        R.drawable.qs_flashlight_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_flashlight_icon_on
+                        } else {
+                            R.drawable.qs_flashlight_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index 7e7034d..fe5445d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.location.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [LocationTileModel] to [QSTileState]. */
-class LocationTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<LocationTileModel> {
+class LocationTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_location_icon_on
-                    } else {
-                        R.drawable.qs_location_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_location_icon_on
+                        } else {
+                            R.drawable.qs_location_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
new file mode 100644
index 0000000..fc42ba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.os.Bundle
+import com.android.internal.R
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class DataSaverDialogDelegate(
+    private val sysuiDialogFactory: SystemUIDialog.Factory,
+    private val context: Context,
+    private val backgroundContext: CoroutineContext,
+    private val dataSaverController: DataSaverController,
+    private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+    override fun createDialog(): SystemUIDialog {
+        return sysuiDialogFactory.create(this, context)
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        with(dialog) {
+            setTitle(R.string.data_saver_enable_title)
+            setMessage(R.string.data_saver_description)
+            setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
+                CoroutineScope(backgroundContext).launch {
+                    dataSaverController.setDataSaverEnabled(true)
+                }
+
+                sharedPreferences
+                    .edit()
+                    .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true)
+                    .apply()
+            }
+            setNeutralButton(R.string.cancel, null)
+            setShowForAllUsers(true)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
new file mode 100644
index 0000000..df25600
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [DataSaverTileModel] to [QSTileState]. */
+class DataSaverTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<DataSaverTileModel> {
+    override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            with(data) {
+                val iconRes: Int
+                if (isEnabled) {
+                    activationState = QSTileState.ActivationState.ACTIVE
+                    iconRes = R.drawable.qs_data_saver_icon_on
+                    secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2]
+                } else {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    iconRes = R.drawable.qs_data_saver_icon_off
+                    secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
+                }
+                val loadedIcon =
+                    Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+                icon = { loadedIcon }
+                contentDescription = label
+                supportedActions =
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
new file mode 100644
index 0000000..91e049b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes data saver state changes providing the [DataSaverTileModel]. */
+class DataSaverTileDataInteractor
+@Inject
+constructor(
+    private val dataSaverController: DataSaverController,
+) : QSTileDataInteractor<DataSaverTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<DataSaverTileModel> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val initialValue = dataSaverController.isDataSaverEnabled
+            trySend(DataSaverTileModel(initialValue))
+
+            val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) }
+
+            dataSaverController.addCallback(callback)
+            awaitClose { dataSaverController.removeCallback(callback) }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..af74409
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles data saver tile clicks. */
+class DataSaverTileUserActionInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Main private val coroutineContext: CoroutineContext,
+    @Background private val backgroundContext: CoroutineContext,
+    private val dataSaverController: DataSaverController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val systemUIDialogFactory: SystemUIDialog.Factory,
+    userFileManager: UserFileManager,
+) : QSTileUserActionInteractor<DataSaverTileModel> {
+    companion object {
+        private const val INTERACTION_JANK_TAG = "start_data_saver"
+        const val PREFS = "data_saver"
+        const val DIALOG_SHOWN = "data_saver_dialog_shown"
+    }
+
+    val sharedPreferences =
+        userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId)
+
+    override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    val wasEnabled: Boolean = data.isEnabled
+                    if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) {
+                        withContext(backgroundContext) {
+                            dataSaverController.setDataSaverEnabled(!wasEnabled)
+                        }
+                        return@with
+                    }
+                    // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator
+                    // must be created and shown on the main thread, so we post it to the UI
+                    // handler
+                    withContext(coroutineContext) {
+                        val dialogContext = action.view?.context ?: context
+                        val dialogDelegate =
+                            DataSaverDialogDelegate(
+                                systemUIDialogFactory,
+                                dialogContext,
+                                backgroundContext,
+                                dataSaverController,
+                                sharedPreferences
+                            )
+                        val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext)
+
+                        if (action.view != null) {
+                            dialogLaunchAnimator.showFromView(
+                                dialog,
+                                action.view!!,
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                    INTERACTION_JANK_TAG
+                                )
+                            )
+                        } else {
+                            dialog.show()
+                        }
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_DATA_SAVER_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
new file mode 100644
index 0000000..040c7bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.model
+
+/**
+ * data saver tile model.
+ *
+ * @param isEnabled is true when the data saver is enabled;
+ */
+@JvmInline value class DataSaverTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index 3f30c75..ffef2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -18,6 +18,7 @@
 
 import android.app.UiModeManager
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import android.text.TextUtils
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
@@ -31,15 +32,19 @@
 import javax.inject.Inject
 
 /** Maps [UiModeNightTileModel] to [QSTileState]. */
-class UiModeNightTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<UiModeNightTileModel> {
+class UiModeNightTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<UiModeNightTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
     }
     override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
         with(data) {
-            QSTileState.build(resources, config.uiConfig) {
+            QSTileState.build(resources, theme, config.uiConfig) {
                 var shouldSetSecondaryLabel = false
 
                 if (isPowerSave) {
@@ -116,8 +121,9 @@
                     if (activationState == QSTileState.ActivationState.ACTIVE)
                         R.drawable.qs_light_dark_theme_icon_on
                     else R.drawable.qs_light_dark_theme_icon_off
-                val iconResource = Icon.Resource(iconRes, null)
-                icon = { iconResource }
+                val loadedIcon =
+                    Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+                icon = { loadedIcon }
 
                 supportedActions =
                     if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 23e0cb6..be1b740 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import android.service.quicksettings.Tile
 import android.view.View
 import android.widget.Switch
@@ -47,14 +48,17 @@
 
         fun build(
             resources: Resources,
+            theme: Theme,
             config: QSTileUIConfig,
             build: Builder.() -> Unit
-        ): QSTileState =
-            build(
-                { Icon.Resource(config.iconRes, null) },
+        ): QSTileState {
+            val iconDrawable = resources.getDrawable(config.iconRes, theme)
+            return build(
+                { Icon.Loaded(iconDrawable, null) },
                 resources.getString(config.labelRes),
                 build,
             )
+        }
 
         fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
             Builder(icon, label).apply(build).build()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e9779cd..5fbb60d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,6 +59,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -116,6 +117,7 @@
     private final AuthController mAuthController;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+    private final SceneContainerFlags mSceneContainerFlags;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
@@ -162,7 +164,8 @@
             Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            SceneContainerFlags sceneContainerFlags) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -180,6 +183,7 @@
         dumpManager.registerDumpable(this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
+        mSceneContainerFlags = sceneContainerFlags;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -287,6 +291,15 @@
         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
+        if (mSceneContainerFlags.isEnabled()) {
+            // This prevents the appearance and disappearance of the software keyboard (also known
+            // as the "IME") from scrolling/panning the window to make room for the keyboard.
+            //
+            // The scene container logic does its own adjustment and animation when the IME appears
+            // or disappears.
+            mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        }
+
         mWindowManager.addView(mWindowRootView, mLp);
 
         mLpChanged.copyFrom(mLp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 8a93ef6..d3459b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -32,6 +32,7 @@
  * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
  */
 @SysUISingleton
+@Deprecated("Use ShadeInteractor instead")
 class ShadeExpansionStateManager @Inject constructor() {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
@@ -49,6 +50,7 @@
      *
      * @see #addExpansionListener
      */
+    @Deprecated("Use ShadeInteractor instead")
     fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
         expansionListeners.add(listener)
         return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
@@ -60,6 +62,7 @@
     }
 
     /** Adds a listener that will be notified when the panel state has changed. */
+    @Deprecated("Use ShadeInteractor instead")
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index c59ef26..d26fded1 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -59,6 +59,11 @@
          * The BcSmartspaceDataPlugin for the standalone weather.
          */
         const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
+
+        /**
+         * The BcSmartspaceDataProvider for the glanceable hub.
+         */
+        const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin"
     }
 
     @BindsOptionalOf
@@ -78,4 +83,8 @@
     abstract fun bindSmartspacePrecondition(
         lockscreenPrecondition: LockscreenPrecondition?
     ): SmartspacePrecondition?
+
+    @BindsOptionalOf
+    @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN)
+    abstract fun optionalBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
index 2fc0ec2..095d30e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -19,9 +19,9 @@
 import android.app.smartspace.SmartspaceTarget
 import android.os.Parcelable
 import android.widget.RemoteViews
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,37 +32,37 @@
     /** Whether [RemoteViews] are passed through smartspace targets. */
     val isSmartspaceRemoteViewsEnabled: Boolean
 
-    /** Smartspace targets for the lockscreen surface. */
-    val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>>
+    /** Smartspace targets for the communal surface. */
+    val communalSmartspaceTargets: Flow<List<SmartspaceTarget>>
 }
 
 @SysUISingleton
 class SmartspaceRepositoryImpl
 @Inject
 constructor(
-    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    private val communalSmartspaceController: CommunalSmartspaceController,
 ) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     override val isSmartspaceRemoteViewsEnabled: Boolean
         get() = android.app.smartspace.flags.Flags.remoteViews()
 
-    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
         MutableStateFlow(emptyList())
-    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _lockscreenSmartspaceTargets
+    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _communalSmartspaceTargets
             .onStart {
-                lockscreenSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
+                communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
             }
             .onCompletion {
-                lockscreenSmartspaceController.removeListener(
+                communalSmartspaceController.removeListener(
                     listener = this@SmartspaceRepositoryImpl
                 )
             }
 
     override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
         targetsNullable?.let { targets ->
-            _lockscreenSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
+            _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
         }
-            ?: run { _lockscreenSmartspaceTargets.value = emptyList() }
+            ?: run { _communalSmartspaceTargets.value = emptyList() }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index d23c85a..cfbd015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -18,16 +18,16 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Handler;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
 import java.util.stream.Stream;
@@ -46,13 +46,12 @@
     protected int mMinimumDisplayTime;
     protected int mStickyForSomeTimeAutoDismissTime;
     protected int mAutoDismissTime;
-    @VisibleForTesting
-    public Handler mHandler;
+    private DelayableExecutor mExecutor;
 
-    public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler,
-            SystemClock systemClock) {
+    public AlertingNotificationManager(HeadsUpManagerLogger logger,
+            SystemClock systemClock, @Main DelayableExecutor executor) {
         mLogger = logger;
-        mHandler = handler;
+        mExecutor = executor;
         mSystemClock = systemClock;
     }
 
@@ -264,6 +263,7 @@
         public long mEarliestRemovalTime;
 
         @Nullable protected Runnable mRemoveAlertRunnable;
+        @Nullable private Runnable mCancelRemoveAlertRunnable;
 
         public void setEntry(@NonNull final NotificationEntry entry) {
             setEntry(entry, () -> removeAlertEntry(entry.getKey()));
@@ -291,13 +291,15 @@
             if (updatePostTime) {
                 mPostTime = Math.max(mPostTime, now);
             }
-            removeAutoRemovalCallbacks("updateEntry (will be rescheduled)");
 
-            if (!isSticky()) {
-                final long finishTime = calculateFinishTime();
-                final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
-                mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
+            if (isSticky()) {
+                removeAutoRemovalCallbacks("updateEntry (sticky)");
+                return;
             }
+
+            final long finishTime = calculateFinishTime();
+            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
         }
 
         /**
@@ -340,21 +342,50 @@
          * Clear any pending removal runnables.
          */
         public void removeAutoRemovalCallbacks(@Nullable String reason) {
-            if (mRemoveAlertRunnable != null) {
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
                 mLogger.logAutoRemoveCanceled(mEntry, reason);
-                mHandler.removeCallbacks(mRemoveAlertRunnable);
             }
         }
 
+        private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
+            if (mRemoveAlertRunnable == null) {
+                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+                return;
+            }
+
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
+                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
+            } else {
+                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
+            }
+
+
+            mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable,
+                    delayMillis);
+        }
+
+        private boolean removeAutoRemovalCallbackInternal() {
+            final boolean scheduled = (mCancelRemoveAlertRunnable != null);
+
+            if (scheduled) {
+                mCancelRemoveAlertRunnable.run();
+                mCancelRemoveAlertRunnable = null;
+            }
+
+            return scheduled;
+        }
+
         /**
          * Remove the alert at the earliest allowed removal time.
          */
         public void removeAsSoonAsPossible() {
             if (mRemoveAlertRunnable != null) {
-                removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)");
-
                 final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
-                mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
+                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e486457..05c3839 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -18,6 +18,7 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_NULL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
@@ -46,6 +47,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -155,8 +157,22 @@
 
             if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
                 if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
-                    boolean changed = updateDpcSettings(getSendingUserId());
-                    if (mCurrentUserId == getSendingUserId()) {
+                    boolean changed = false;
+                    int sendingUserId = getSendingUserId();
+                    if (sendingUserId == USER_ALL) {
+                        // When a Device Owner triggers changes it's sent as USER_ALL. Normalize
+                        // the user before calling into DPM
+                        sendingUserId = mCurrentUserId;
+                        @SuppressLint("MissingPermission")
+                        List<UserInfo> users = mUserManager.getUsers();
+                        for (int i = users.size() - 1; i >= 0; i--) {
+                            changed |= updateDpcSettings(users.get(i).id);
+                        }
+                    } else {
+                        changed |= updateDpcSettings(sendingUserId);
+                    }
+
+                    if (mCurrentUserId == sendingUserId) {
                         changed |= updateLockscreenNotificationSetting();
                     }
                     if (changed) {
@@ -374,13 +390,13 @@
         mContext.getContentResolver().registerContentObserver(
                 SHOW_LOCKSCREEN, false,
                 mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
+                USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
                 SHOW_PRIVATE_LOCKSCREEN,
                 true,
                 mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
+                USER_ALL);
 
         if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
             mContext.getContentResolver().registerContentObserver(
@@ -441,7 +457,7 @@
 
     public boolean isCurrentProfile(int userId) {
         synchronized (mLock) {
-            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+            return userId == USER_ALL || mCurrentProfiles.get(userId) != null;
         }
     }
 
@@ -526,7 +542,7 @@
      */
     public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
         if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
-            if (userHandle == UserHandle.USER_ALL) {
+            if (userHandle == USER_ALL) {
                 userHandle = mCurrentUserId;
             }
             if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
@@ -540,7 +556,7 @@
             return mUsersUsersAllowingPrivateNotifications.get(userHandle)
                     && mUsersDpcAllowingPrivateNotifications.get(userHandle);
         } else {
-            if (userHandle == UserHandle.USER_ALL) {
+            if (userHandle == USER_ALL) {
                 return true;
             }
 
@@ -574,7 +590,7 @@
     }
 
     private boolean adminAllowsKeyguardFeature(int userHandle, int feature) {
-        if (userHandle == UserHandle.USER_ALL) {
+        if (userHandle == USER_ALL) {
             return true;
         }
         final int dpmFlags =
@@ -591,7 +607,7 @@
     }
 
     public boolean isLockscreenPublicMode(int userId) {
-        if (userId == UserHandle.USER_ALL) {
+        if (userId == USER_ALL) {
             return mLockscreenPublicMode.get(mCurrentUserId, false);
         }
         return mLockscreenPublicMode.get(userId, false);
@@ -610,7 +626,7 @@
         if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
             // Unlike 'show private', settings does not show a copy of this setting for each
             // profile, so it inherits from the parent user.
-            if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+            if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
                 userHandle = mCurrentUserId;
             }
             if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
index 490994d..fc474d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
@@ -89,5 +89,12 @@
          * "wifi_start_connect_ssid" set as an extra
          */
         fun onSettingsActivityTriggered(settingsIntent: Intent?)
+
+        /**
+         * Called whenever a Wi-Fi scan is triggered.
+         *
+         * @param isScan Whether Wi-Fi scan is triggered or not.
+         */
+        fun onWifiScan(isScan: Boolean)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 91ca148..3a31851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -213,6 +213,12 @@
         }
     }
 
+    private void fireWifiScanCallback(boolean isScan) {
+        for (AccessPointCallback callback : mCallbacks) {
+            callback.onWifiScan(isScan);
+        }
+    }
+
     void dump(PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println("AccessPointControllerImpl:");
@@ -240,6 +246,14 @@
     }
 
     @Override
+    public void onWifiEntriesChanged(@WifiPickerTracker.WifiEntriesChangedReason int reason) {
+        onWifiEntriesChanged();
+        if (reason == WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) {
+            fireWifiScanCallback(false /* isScan */);
+        }
+    }
+
+    @Override
     public void onNumSavedNetworksChanged() {
         // Do nothing
     }
@@ -249,6 +263,11 @@
         // Do nothing
     }
 
+    @Override
+    public void onScanRequested() {
+        fireWifiScanCallback(true /* isScan */);
+    }
+
     private final WifiEntry.ConnectCallback mConnectCallback = new WifiEntry.ConnectCallback() {
         @Override
         public void onConnectResult(int status) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index a3adea0..642eacc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
@@ -85,6 +89,7 @@
     companion object {
 
         const val AIRPLANE_MODE_TILE_SPEC = "airplane"
+        const val DATA_SAVER_TILE_SPEC = "saver"
 
         /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
         @Provides
@@ -132,5 +137,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(DATA_SAVER_TILE_SPEC)
+        fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_data_saver_icon_off,
+                        labelRes = R.string.data_saver,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject DataSaverTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(DATA_SAVER_TILE_SPEC)
+        fun provideDataSaverTileViewModel(
+            factory: QSTileViewModelFactory.Static<DataSaverTileModel>,
+            mapper: DataSaverTileMapper,
+            stateInteractor: DataSaverTileDataInteractor,
+            userActionInteractor: DataSaverTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(DATA_SAVER_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
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 adf6cca..625fdc1 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
@@ -20,6 +20,8 @@
 import android.content.Context
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
@@ -28,6 +30,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -39,7 +42,9 @@
 constructor(
     configurationRepository: ConfigurationRepository,
     private val context: Context,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    keyguardInteractor: KeyguardInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
 ) {
 
     private val _topPosition = MutableStateFlow(0f)
@@ -75,6 +80,19 @@
             }
             .distinctUntilChanged()
 
+    /**
+     * The notification shelf can extend over the lock icon area if:
+     * * UDFPS supported. Ambient indication will always appear below
+     * * UDFPS not supported and ambient indication not visible, which will appear above lock icon
+     */
+    val useExtraShelfSpace: Flow<Boolean> =
+        combine(
+            keyguardInteractor.ambientIndicationVisible,
+            deviceEntryUdfpsInteractor.isUdfpsSupported,
+        ) { ambientIndicationVisible, isUdfpsSupported ->
+            isUdfpsSupported || !ambientIndicationVisible
+        }
+
     val isSplitShadeEnabled: Flow<Boolean> =
         configurationBasedDimensions
             .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index af56a3f..12927b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -101,12 +101,13 @@
 
                     launch {
                         viewModel
-                            .getMaxNotifications { space ->
+                            .getMaxNotifications { space, extraShelfSpace ->
+                                val shelfHeight = controller.getShelfHeight().toFloat()
                                 notificationStackSizeCalculator.computeMaxKeyguardNotifications(
                                     controller.getView(),
                                     space,
-                                    0f, // Vertical space for shelf is already accounted for
-                                    controller.getShelfHeight().toFloat(),
+                                    if (extraShelfSpace) shelfHeight else 0f,
+                                    shelfHeight,
                                 )
                             }
                             .collect { controller.setMaxDisplayedNotifications(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 9594bc3..eff91e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -229,7 +229,7 @@
      * When expanding or when the user is interacting with the shade, keep the count stable; do not
      * emit a value.
      */
-    fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> {
+    fun getMaxNotifications(calculateSpace: (Float, Boolean) -> Int): Flow<Int> {
         val showLimitedNotifications = isOnLockscreenWithoutShade
         val showUnlimitedNotifications =
             combine(
@@ -245,11 +245,17 @@
                 shadeInteractor.isUserInteracting,
                 bounds,
                 interactor.notificationStackChanged.onStart { emit(Unit) },
-            ) { showLimitedNotifications, showUnlimitedNotifications, isUserInteracting, bounds, _
-                ->
+                interactor.useExtraShelfSpace,
+            ) { flows ->
+                val showLimitedNotifications = flows[0] as Boolean
+                val showUnlimitedNotifications = flows[1] as Boolean
+                val isUserInteracting = flows[2] as Boolean
+                val bounds = flows[3] as NotificationContainerBounds
+                val useExtraShelfSpace = flows[5] as Boolean
+
                 if (!isUserInteracting) {
                     if (showLimitedNotifications) {
-                        emit(calculateSpace(bounds.bottom - bounds.top))
+                        emit(calculateSpace(bounds.bottom - bounds.top, useExtraShelfSpace))
                     } else if (showUnlimitedNotifications) {
                         emit(-1)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 644c896..e66d9e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
@@ -119,12 +120,13 @@
             @Main Handler handler,
             GlobalSettings globalSettings,
             SystemClock systemClock,
+            @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
             ShadeInteractor shadeInteractor) {
-        super(context, logger, handler, globalSettings, systemClock, accessibilityManagerWrapper,
-                uiEventLogger);
+        super(context, logger, handler, globalSettings, systemClock, executor,
+                accessibilityManagerWrapper, uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 3b96f57..877bd7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -42,6 +42,9 @@
 import com.android.systemui.util.leak.RotationUtils.getExactRotation
 import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
 import com.android.app.tracing.traceSection
+import com.android.systemui.BottomMarginCommand
+import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.statusbar.commandline.CommandRegistry
 import java.io.PrintWriter
 import java.lang.Math.max
 import javax.inject.Inject
@@ -64,7 +67,8 @@
 class StatusBarContentInsetsProvider @Inject constructor(
     val context: Context,
     val configurationController: ConfigurationController,
-    val dumpManager: DumpManager
+    val dumpManager: DumpManager,
+    val commandRegistry: CommandRegistry,
 ) : CallbackController<StatusBarContentInsetsChangedListener>,
         ConfigurationController.ConfigurationListener,
         Dumpable {
@@ -80,6 +84,13 @@
     init {
         configurationController.addCallback(this)
         dumpManager.registerDumpable(TAG, this)
+        commandRegistry.registerCommand(StatusBarInsetsCommand.NAME) {
+            StatusBarInsetsCommand(object : StatusBarInsetsCommand.Callback {
+                override fun onExecute(command: StatusBarInsetsCommand, printWriter: PrintWriter) {
+                    executeCommand(command, printWriter)
+                }
+            })
+        }
     }
 
     override fun addCallback(listener: StatusBarContentInsetsChangedListener) {
@@ -271,8 +282,41 @@
                 statusBarContentHeight)
     }
 
+    private fun executeCommand(command: StatusBarInsetsCommand, printWriter: PrintWriter) {
+        command.bottomMargin?.let { executeBottomMarginCommand(it, printWriter) }
+    }
+
+    private fun executeBottomMarginCommand(command: BottomMarginCommand, printWriter: PrintWriter) {
+        val rotation = command.rotationValue
+        if (rotation == null) {
+            printWriter.println(
+                    "Rotation should be one of ${BottomMarginCommand.ROTATION_DEGREES_OPTIONS}"
+            )
+            return
+        }
+        val marginBottomDp = command.marginBottomDp
+        if (marginBottomDp == null) {
+            printWriter.println("Margin bottom not set.")
+            return
+        }
+        setBottomMarginOverride(rotation, marginBottomDp)
+    }
+
+    private val marginBottomOverrides = mutableMapOf<Int, Int>()
+
+    private fun setBottomMarginOverride(rotation: Int, marginBottomDp: Float) {
+        insetsCache.evictAll()
+        val marginBottomPx = (marginBottomDp * context.resources.displayMetrics.density).toInt()
+        marginBottomOverrides[rotation] = marginBottomPx
+        notifyInsetsChanged()
+    }
+
     @Px
     private fun getBottomAlignedMargin(targetRotation: Int, resources: Resources): Int {
+        val override = marginBottomOverrides[targetRotation]
+        if (override != null) {
+            return override
+        }
         val dimenRes =
                 when (targetRotation) {
                     Surface.ROTATION_0 -> R.dimen.status_bar_bottom_aligned_margin_rotation_0
@@ -294,6 +338,7 @@
             pw.println("$key -> $rect")
         }
         pw.println(insetsCache)
+        pw.println("Bottom margin overrides: $marginBottomOverrides")
     }
 
     private fun getCacheKey(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index daadedb..4999123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -36,6 +36,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+import android.view.WindowInsetsController;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -859,10 +860,19 @@
         }
     }
 
+    private void setRootViewAnimationDisabled(boolean disabled) {
+        ViewGroup windowRootView = mNotificationShadeWindowController.getWindowRootView();
+        if (windowRootView != null) {
+            WindowInsetsController insetsController = windowRootView.getWindowInsetsController();
+            if (insetsController != null) {
+                insetsController.setAnimationsDisabled(disabled);
+            }
+        }
+    }
+
     @Override
     public void onStartedWakingUp() {
-        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
-                .setAnimationsDisabled(false);
+        setRootViewAnimationDisabled(false);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
             navBarView.forEachView(view ->
@@ -875,8 +885,7 @@
 
     @Override
     public void onStartedGoingToSleep() {
-        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
-                .setAnimationsDisabled(true);
+        setRootViewAnimationDisabled(true);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
             navBarView.forEachView(view ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 8054b04..e971128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
@@ -87,9 +88,10 @@
             @Main Handler handler,
             GlobalSettings globalSettings,
             SystemClock systemClock,
+            @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger) {
-        super(logger, handler, systemClock);
+        super(logger, systemClock, executor);
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index ef07eed..f6154afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,26 @@
         })
     }
 
+    fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            long1 = delayMillis
+            str2 = reason
+        }, {
+            "schedule auto remove of $str1 in $long1 ms reason: $str2"
+        })
+    }
+
+    fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            long1 = delayMillis
+            str2 = reason
+        }, {
+            "reschedule auto remove of $str1 in $long1 ms reason: $str2"
+        })
+    }
+
     fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 80c6802..756c440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -484,7 +484,12 @@
         }
 
         @Override
-        public void onBiometricsCleared() {
+        public void onFingerprintsCleared() {
+            update(false /* alwaysUpdate */);
+        }
+
+        @Override
+        public void onFacesCleared() {
             update(false /* alwaysUpdate */);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index f5edb7b..fa6d055 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -83,17 +83,19 @@
 
     /**
      * Allow the media player to be shown in the QS area, controlled by 2 flags.
-     * Off by default, but can be disabled by setting to 0
+     * On by default, but can be disabled by setting either flag to 0/false.
      */
     public static boolean useQsMediaPlayer(Context context) {
-        // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value
         // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply
         // cache the first result we fetch and use that going forward. Do this to avoid unnecessary
         // binder calls which may happen on the critical path.
         if (sUseQsMediaPlayer == null) {
-            int flag = Settings.Global.getInt(context.getContentResolver(),
+            // TODO(b/192412820): Consolidate SHOW_MEDIA_ON_QUICK_SETTINGS into compile-time value.
+            final int settingsFlag = Settings.Global.getInt(context.getContentResolver(),
                     Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
-            sUseQsMediaPlayer = flag > 0;
+            final boolean configFlag = context.getResources()
+                    .getBoolean(com.android.internal.R.bool.config_quickSettingsShowMediaPlayer);
+            sUseQsMediaPlayer = settingsFlag > 0 && configFlag;
         }
         return sUseQsMediaPlayer;
     }
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 235aa21..f8856c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -34,8 +34,8 @@
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import androidx.test.filters.SmallTest;
 
@@ -67,7 +67,7 @@
     @Mock
     private CommandQueue mCommandQueue;
     @Mock
-    private IWindowMagnificationConnectionCallback mConnectionCallback;
+    private IMagnificationConnectionCallback mConnectionCallback;
     @Mock
     private WindowMagnificationController mWindowMagnificationController;
     @Mock
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 39c8f5d..d0e1678 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -44,7 +44,7 @@
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
+import android.view.accessibility.IMagnificationConnectionCallback;
 
 import androidx.test.filters.SmallTest;
 
@@ -75,7 +75,7 @@
     @Mock
     private SysUiState mSysUiState;
     @Mock
-    private IWindowMagnificationConnectionCallback mConnectionCallback;
+    private IMagnificationConnectionCallback mConnectionCallback;
     @Mock
     private OverviewProxyService mOverviewProxyService;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 395d712..ca95822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -18,10 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import org.junit.Test
@@ -84,33 +84,33 @@
             listOf(
                     Phone to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Tablet to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STACKED,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BELOW_USER_SWITCHER,
                         ),
                     Folded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Unfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STANDARD,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = STANDARD_BOUNCER,
                         ),
                     TallerFolded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     TallerUnfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = SIDE_BY_SIDE,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BESIDE_USER_SWITCHER,
                         ),
                 )
                 .flatMap { (device, expected) ->
@@ -124,13 +124,13 @@
                             )
                         )
 
-                        if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld,
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
@@ -144,13 +144,13 @@
                             )
                         )
 
-                        if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld.flip(),
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
index e931384..65f68f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -39,6 +40,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verify
@@ -73,13 +76,20 @@
         if (Looper.myLooper() == null) Looper.prepare()
 
         mContrastDialogDelegate =
-                ContrastDialogDelegate(
-                        sysuiDialogFactory,
-                        mainExecutor,
-                        mockUiModeManager,
-                        mockUserTracker,
-                        mockSecureSettings
-                )
+            ContrastDialogDelegate(
+                sysuiDialogFactory,
+                mainExecutor,
+                mockUiModeManager,
+                mockUserTracker,
+                mockSecureSettings
+            )
+
+        mContrastDialogDelegate.createDialog()
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(sysuiDialog).setView(viewCaptor.capture())
+        whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
+            viewCaptor.value.requireViewById(it.getArgument(0))
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 7750d25..ab6bc2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -71,7 +71,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -86,7 +86,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
                 )
                 .compose()
 
@@ -102,7 +102,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -117,7 +117,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -132,7 +132,11 @@
     fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -203,7 +207,11 @@
     fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -212,7 +220,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -232,7 +240,11 @@
     fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
         // GIVEN max velocity and slider progress
         val progress = 1f
-        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+        val expectedScale =
+            sliderHapticFeedbackProvider.scaleOnDragTexture(
+                config.maxVelocityToScale,
+                progress,
+            )
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -241,7 +253,7 @@
             VibrationEffect.startComposition()
                 .addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    scaleAtBookends(config.maxVelocityToScale)
+                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                 )
                 .compose()
 
@@ -289,28 +301,12 @@
         assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
     }
 
-    private fun scaleAtBookends(velocity: Float): Float {
-        val range = config.upperBookendScale - config.lowerBookendScale
-        val interpolatedVelocity =
-            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
-        return interpolatedVelocity * range + config.lowerBookendScale
-    }
-
-    private fun scaleAtProgressChange(velocity: Float, progress: Float): Float {
-        val range = config.progressBasedDragMaxScale - config.progressBasedDragMinScale
-        val interpolatedVelocity =
-            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
-        val interpolatedProgress = progressInterpolator.getInterpolation(progress)
-        val bump = interpolatedVelocity * config.additionalVelocityMaxBump
-        return interpolatedProgress * range + config.progressBasedDragMinScale + bump
-    }
-
     private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
         val ticks = VibrationEffect.startComposition()
         repeat(config.numberOfLowTicks) {
             ticks.addPrimitive(
                 VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                scaleAtProgressChange(velocity, progress)
+                sliderHapticFeedbackProvider.scaleOnDragTexture(velocity, progress),
             )
         }
         return ticks.compose()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index d246f0e..ae5f625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -214,6 +215,7 @@
     private @Mock CoroutineDispatcher mDispatcher;
     private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+    private @Mock SceneContainerFlags mSceneContainerFlags;
 
     private FakeFeatureFlags mFeatureFlags;
     private final int mDefaultUserId = 100;
@@ -258,7 +260,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker);
+                mUserTracker,
+                mSceneContainerFlags);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index de12b8f..4ab8e28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -17,8 +17,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -62,6 +64,7 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -74,8 +77,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -99,7 +105,8 @@
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var trustManager: TrustManager
 
     @Before
     fun setup() {
@@ -146,7 +153,7 @@
                         keyguardUpdateMonitor,
                         FakeTrustRepository(),
                         testScope.backgroundScope,
-                        mSelectedUserInteractor,
+                        selectedUserInteractor,
                         underTest,
                     )
                 },
@@ -169,6 +176,7 @@
                 faceWakeUpTriggersConfig,
                 powerInteractor,
                 fakeBiometricSettingsRepository,
+                trustManager,
             )
     }
 
@@ -498,6 +506,22 @@
             assertThat(faceAuthRepository.isLockedOut.value).isTrue()
         }
 
+    @Test
+    fun whenIsAuthenticatedFalse_clearFaceBiometrics() =
+        testScope.runTest {
+            underTest.start()
+
+            faceAuthRepository.isAuthenticated.value = true
+            runCurrent()
+            verify(trustManager, never())
+                .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+
+            faceAuthRepository.isAuthenticated.value = false
+            runCurrent()
+
+            verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+        }
+
     companion object {
         private const val primaryUserId = 1
         private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 976dc5f..b8a8bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1137,7 +1137,7 @@
             runCurrent()
 
             // WHEN primary bouncer shows
-            bouncerRepository.setPrimaryShow(true) // beverlyt
+            bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
             val info =
@@ -1232,6 +1232,36 @@
         }
 
     @Test
+    fun dreamingToAod() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the device starts DOZE_AOD
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    from = DozeStateModel.INITIALIZED,
+                    to = DozeStateModel.DOZE_AOD,
+                )
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to AOD should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.AOD)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun lockscreenToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6878007..459a74c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -184,6 +184,13 @@
         }
 
     @Test
+    fun translationYInitialValueIsZero() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY)
+            assertThat(translationY).isEqualTo(0)
+        }
+
+    @Test
     fun translationAndScaleFromBurnInNotDozing() =
         testScope.runTest {
             val translationX by collectLastValue(underTest.translationX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 3bfdb84..310e0b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -124,6 +125,7 @@
     @Captor lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
 
     private val clock = FakeSystemClock()
+    private lateinit var bgExecutor: FakeExecutor
     private lateinit var mediaCarouselController: MediaCarouselController
 
     @Before
@@ -131,6 +133,7 @@
         MockitoAnnotations.initMocks(this)
         context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
         transitionRepository = FakeKeyguardTransitionRepository()
+        bgExecutor = FakeExecutor(clock)
         mediaCarouselController =
             MediaCarouselController(
                 context,
@@ -140,6 +143,7 @@
                 activityStarter,
                 clock,
                 executor,
+                bgExecutor,
                 mediaDataManager,
                 configurationController,
                 falsingManager,
@@ -458,6 +462,7 @@
             mediaHostState,
             animate = false
         )
+        bgExecutor.runAllReady()
         verify(logger).logCarouselPosition(LOCATION_QS)
     }
 
@@ -468,6 +473,7 @@
             mediaHostState,
             animate = false
         )
+        bgExecutor.runAllReady()
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
     }
 
@@ -478,6 +484,7 @@
             mediaHostState,
             animate = false
         )
+        bgExecutor.runAllReady()
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
     }
 
@@ -488,6 +495,7 @@
             mediaHostState,
             animate = false
         )
+        bgExecutor.runAllReady()
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index 0a8c0ab..e4432f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -31,18 +31,20 @@
 import android.permission.PermissionManager
 import android.testing.AndroidTestingRunner
 import android.view.View
+import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.LaunchableView
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -56,12 +58,12 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -83,60 +85,48 @@
         private val TEST_INTENT = Intent("test_intent_action")
     }
 
-    @Mock
-    private lateinit var dialog: PrivacyDialogV2
-    @Mock
-    private lateinit var permissionManager: PermissionManager
-    @Mock
-    private lateinit var packageManager: PackageManager
-    @Mock
-    private lateinit var privacyItemController: PrivacyItemController
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var privacyLogger: PrivacyLogger
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var appOpsController: AppOpsController
+    @Mock private lateinit var dialog: PrivacyDialogV2
+    @Mock private lateinit var permissionManager: PermissionManager
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var privacyItemController: PrivacyItemController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var privacyLogger: PrivacyLogger
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var appOpsController: AppOpsController
     @Captor
     private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed>
-    @Captor
-    private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
-    @Captor
-    private lateinit var intentCaptor: ArgumentCaptor<Intent>
-    @Mock
-    private lateinit var uiEventLogger: UiEventLogger
-    @Mock
-    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Captor private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
+    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
 
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private lateinit var controller: PrivacyDialogControllerV2
     private var nextUid: Int = 0
 
-    private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider {
-        var list: List<PrivacyDialogV2.PrivacyElement>? = null
-        var manageApp: ((String, Int, Intent) -> Unit)? = null
-        var closeApp: ((String, Int) -> Unit)? = null
-        var openPrivacyDashboard: (() -> Unit)? = null
+    private val dialogProvider =
+        object : PrivacyDialogControllerV2.DialogProvider {
+            var list: List<PrivacyDialogV2.PrivacyElement>? = null
+            var manageApp: ((String, Int, Intent) -> Unit)? = null
+            var closeApp: ((String, Int) -> Unit)? = null
+            var openPrivacyDashboard: (() -> Unit)? = null
 
-        override fun makeDialog(
-            context: Context,
-            list: List<PrivacyDialogV2.PrivacyElement>,
-            manageApp: (String, Int, Intent) -> Unit,
-            closeApp: (String, Int) -> Unit,
-            openPrivacyDashboard: () -> Unit
-        ): PrivacyDialogV2 {
-            this.list = list
-            this.manageApp = manageApp
-            this.closeApp = closeApp
-            this.openPrivacyDashboard = openPrivacyDashboard
-            return dialog
+            override fun makeDialog(
+                context: Context,
+                list: List<PrivacyDialogV2.PrivacyElement>,
+                manageApp: (String, Int, Intent) -> Unit,
+                closeApp: (String, Int) -> Unit,
+                openPrivacyDashboard: () -> Unit
+            ): PrivacyDialogV2 {
+                this.list = list
+                this.manageApp = manageApp
+                this.closeApp = closeApp
+                this.openPrivacyDashboard = openPrivacyDashboard
+                return dialog
+            }
         }
-    }
 
     @Before
     fun setUp() {
@@ -144,7 +134,8 @@
         nextUid = 0
         setUpDefaultMockResponses()
 
-        controller = PrivacyDialogControllerV2(
+        controller =
+            PrivacyDialogControllerV2(
                 permissionManager,
                 packageManager,
                 privacyItemController,
@@ -158,7 +149,7 @@
                 uiEventLogger,
                 dialogLaunchAnimator,
                 dialogProvider
-        )
+            )
     }
 
     @After
@@ -197,7 +188,7 @@
         verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
         backgroundExecutor.runAllReady()
         verify(packageManager, atLeastOnce())
-                .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+            .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
     }
 
     @Test
@@ -208,20 +199,25 @@
         controller.showDialog(context)
         exhaustExecutors()
 
-        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean())
+        verify(dialogLaunchAnimator, never()).show(any(), any(), anyBoolean())
         verify(dialog).show()
     }
 
     @Test
     fun testShowDialogShowsDialogWithView() {
-        val view = View(context)
+        val parent = LinearLayout(context)
+        val view =
+            object : View(context), LaunchableView {
+                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+            }
+        parent.addView(view)
         val usage = createMockPermGroupUsage()
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context, view)
         exhaustExecutors()
 
-        verify(dialogLaunchAnimator).showFromView(dialog, view)
+        verify(dialogLaunchAnimator).show(eq(dialog), any(), anyBoolean())
         verify(dialog, never()).show()
     }
 
@@ -276,7 +272,8 @@
 
     @Test
     fun testSingleElementInList() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 packageName = TEST_PACKAGE_NAME,
                 uid = generateUidForUser(USER_ID),
                 permissionGroupName = PERM_CAMERA,
@@ -285,7 +282,7 @@
                 isPhoneCall = false,
                 attributionTag = null,
                 proxyLabel = TEST_PROXY_LABEL
-        )
+            )
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
@@ -304,33 +301,38 @@
             assertThat(list.get(0).isPhoneCall).isFalse()
             assertThat(list.get(0).isService).isFalse()
             assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
         }
     }
 
     private fun isIntentEqual(actual: Intent, expected: Intent): Boolean {
         return actual.action == expected.action &&
-                actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
+            actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
                 expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) &&
-                actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
+            actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
                 expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle
     }
 
     @Test
     fun testTwoElementsDifferentType_sorted() {
-        val usage_camera = createMockPermGroupUsage(
+        val usage_camera =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_camera",
                 permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
+            )
+        val usage_microphone =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_microphone",
                 permissionGroupName = PERM_MICROPHONE
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_microphone, usage_camera)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_microphone, usage_camera))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -343,17 +345,12 @@
 
     @Test
     fun testTwoElementsSameType_oneActive() {
-        val usage_active = createMockPermGroupUsage(
-                packageName = "${TEST_PACKAGE_NAME}_active",
-                isActive = true
-        )
-        val usage_recent = createMockPermGroupUsage(
-                packageName = "${TEST_PACKAGE_NAME}_recent",
-                isActive = false
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_recent, usage_active)
-        )
+        val usage_active =
+            createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_active", isActive = true)
+        val usage_recent =
+            createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_recent, usage_active))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -364,19 +361,20 @@
 
     @Test
     fun testTwoElementsSameType_twoActive() {
-        val usage_active = createMockPermGroupUsage(
+        val usage_active =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_active",
                 isActive = true,
                 lastAccessTimeMillis = 0L
-        )
-        val usage_active_moreRecent = createMockPermGroupUsage(
+            )
+        val usage_active_moreRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_active_recent",
                 isActive = true,
                 lastAccessTimeMillis = 1L
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_active, usage_active_moreRecent)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_active, usage_active_moreRecent))
         controller.showDialog(context)
         exhaustExecutors()
         assertThat(dialogProvider.list).hasSize(2)
@@ -386,24 +384,26 @@
 
     @Test
     fun testManyElementsSameType_bothRecent() {
-        val usage_recent = createMockPermGroupUsage(
+        val usage_recent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_recent",
                 isActive = false,
                 lastAccessTimeMillis = 0L
-        )
-        val usage_moreRecent = createMockPermGroupUsage(
+            )
+        val usage_moreRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_moreRecent",
                 isActive = false,
                 lastAccessTimeMillis = 1L
-        )
-        val usage_mostRecent = createMockPermGroupUsage(
+            )
+        val usage_mostRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_mostRecent",
                 isActive = false,
                 lastAccessTimeMillis = 2L
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_recent, usage_mostRecent, usage_moreRecent)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_recent, usage_mostRecent, usage_moreRecent))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -414,19 +414,12 @@
 
     @Test
     fun testMicAndCameraDisabled() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
 
         controller.showDialog(context)
@@ -438,45 +431,29 @@
 
     @Test
     fun testLocationDisabled() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.locationAvailable).thenReturn(false)
 
         controller.showDialog(context)
         exhaustExecutors()
 
         assertThat(dialogProvider.list).hasSize(2)
-        dialogProvider.list?.forEach {
-            assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION)
-        }
+        dialogProvider.list?.forEach { assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) }
     }
 
     @Test
     fun testAllIndicatorsAvailable() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
         `when`(privacyItemController.locationAvailable).thenReturn(true)
 
@@ -488,19 +465,12 @@
 
     @Test
     fun testNoIndicatorsAvailable() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
         `when`(privacyItemController.locationAvailable).thenReturn(false)
 
@@ -512,11 +482,9 @@
 
     @Test
     fun testNotCurrentUser() {
-        val usage_other = createMockPermGroupUsage(
-                uid = generateUidForUser(ENT_USER_ID + 1)
-        )
+        val usage_other = createMockPermGroupUsage(uid = generateUidForUser(ENT_USER_ID + 1))
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
-                .thenReturn(listOf(usage_other))
+            .thenReturn(listOf(usage_other))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -559,9 +527,7 @@
         // Calls happen in
         val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(userTracker.userProfiles).thenReturn(listOf(
-                UserInfo(ENT_USER_ID, "", 0)
-        ))
+        `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(ENT_USER_ID, "", 0)))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -577,8 +543,12 @@
         exhaustExecutors()
 
         dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
-        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
-                USER_ID, TEST_PACKAGE_NAME)
+        verify(uiEventLogger)
+            .log(
+                PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                USER_ID,
+                TEST_PACKAGE_NAME
+            )
     }
 
     @Test
@@ -589,8 +559,12 @@
         exhaustExecutors()
 
         dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID)
-        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
-                USER_ID, TEST_PACKAGE_NAME)
+        verify(uiEventLogger)
+            .log(
+                PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+                USER_ID,
+                TEST_PACKAGE_NAME
+            )
     }
 
     @Test
@@ -629,9 +603,13 @@
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
@@ -648,45 +626,58 @@
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(
-                        TEST_PACKAGE_NAME, ENT_USER_ID)))
-                        .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(
+                            TEST_PACKAGE_NAME,
+                            ENT_USER_ID
+                        )
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testDefaultIntentOnInvalidAttributionTag() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 attributionTag = "INVALID_ATTRIBUTION_TAG",
                 proxyLabel = TEST_PROXY_LABEL
-        )
+            )
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testServiceIntentOnCorrectSubAttribution() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 attributionTag = TEST_ATTRIBUTION_TAG,
                 attributionLabel = "TEST_LABEL"
-        )
+            )
 
         val activityInfo = createMockActivityInfo()
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
@@ -694,57 +685,61 @@
             val navigationIntent = list.get(0).navigationIntent!!
             assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE)
             assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME))
-                    .isEqualTo(PERM_CAMERA)
+                .isEqualTo(PERM_CAMERA)
             assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS))
-                    .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
+                .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
             assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false))
-                    .isTrue()
+                .isTrue()
             assertThat(list.get(0).isService).isTrue()
         }
     }
 
     @Test
     fun testDefaultIntentOnMissingAttributionLabel() {
-        val usage = createMockPermGroupUsage(
-                attributionTag = TEST_ATTRIBUTION_TAG
-        )
+        val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG)
 
         val activityInfo = createMockActivityInfo()
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testDefaultIntentOnIncorrectPermission() {
-        val usage = createMockPermGroupUsage(
-                attributionTag = TEST_ATTRIBUTION_TAG
-        )
+        val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG)
 
-        val activityInfo = createMockActivityInfo(
-                permission = "INCORRECT_PERMISSION"
-        )
+        val activityInfo = createMockActivityInfo(permission = "INCORRECT_PERMISSION")
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
@@ -758,15 +753,18 @@
         `when`(appOpsController.isMicMuted).thenReturn(false)
 
         `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
+            .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
 
         `when`(privacyItemController.locationAvailable).thenReturn(true)
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
 
-        `when`(userTracker.userProfiles).thenReturn(listOf(
-                UserInfo(USER_ID, "", 0),
-                UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
-        ))
+        `when`(userTracker.userProfiles)
+            .thenReturn(
+                listOf(
+                    UserInfo(USER_ID, "", 0),
+                    UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
+                )
+            )
 
         `when`(keyguardStateController.isUnlocked).thenReturn(true)
     }
@@ -781,9 +779,7 @@
         return user * UserHandle.PER_USER_RANGE + nextUid++
     }
 
-    private fun createMockResolveInfo(
-        activityInfo: ActivityInfo? = null
-    ): ResolveInfo {
+    private fun createMockResolveInfo(activityInfo: ActivityInfo? = null): ResolveInfo {
         val resolveInfo = mock(ResolveInfo::class.java)
         resolveInfo.activityInfo = activityInfo
         return resolveInfo
@@ -822,4 +818,4 @@
         `when`(usage.proxyLabel).thenReturn(proxyLabel)
         return usage
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 6dc7a06..b24b877 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -63,13 +63,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.settingslib.wifi.dpp.WifiDppIntentHelper;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -738,6 +738,44 @@
     }
 
     @Test
+    public void onWifiScan_isWifiEnabledFalse_callbackOnWifiScanFalse() {
+        reset(mInternetDialogCallback);
+        when(mWifiStateWorker.isWifiEnabled()).thenReturn(false);
+
+        mInternetDialogController.onWifiScan(true);
+
+        verify(mInternetDialogCallback).onWifiScan(false);
+    }
+
+    @Test
+    public void onWifiScan_isDeviceLockedTrue_callbackOnWifiScanFalse() {
+        reset(mInternetDialogCallback);
+        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+        mInternetDialogController.onWifiScan(true);
+
+        verify(mInternetDialogCallback).onWifiScan(false);
+    }
+
+    @Test
+    public void onWifiScan_onWifiScanFalse_callbackOnWifiScanFalse() {
+        reset(mInternetDialogCallback);
+
+        mInternetDialogController.onWifiScan(false);
+
+        verify(mInternetDialogCallback).onWifiScan(false);
+    }
+
+    @Test
+    public void onWifiScan_onWifiScanTrue_callbackOnWifiScanTrue() {
+        reset(mInternetDialogCallback);
+
+        mInternetDialogController.onWifiScan(true);
+
+        verify(mInternetDialogCallback).onWifiScan(true);
+    }
+
+    @Test
     public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
         when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
                 .thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 039e58a..916bb79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -6,12 +6,10 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -33,9 +31,9 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -48,7 +46,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
@@ -613,66 +610,21 @@
     }
 
     @Test
-    public void showProgressBar_wifiDisabled_hideProgressBar() {
-        Mockito.reset(mHandler);
-        when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
+    public void onWifiScan_isScanTrue_setProgressBarVisibleTrue() {
+        mInternetDialog.mIsProgressBarVisible = false;
 
-        mInternetDialog.showProgressBar();
+        mInternetDialog.onWifiScan(true);
 
-        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
-        verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
     }
 
     @Test
-    public void showProgressBar_deviceLocked_hideProgressBar() {
-        Mockito.reset(mHandler);
-        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+    public void onWifiScan_isScanFalse_setProgressBarVisibleFalse() {
+        mInternetDialog.mIsProgressBarVisible = true;
 
-        mInternetDialog.showProgressBar();
+        mInternetDialog.onWifiScan(false);
 
         assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
-        verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
-    }
-
-    @Test
-    public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
-        Mockito.reset(mHandler);
-        when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-
-        mInternetDialog.showProgressBar();
-
-        // Show progress bar
-        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
-
-        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).postDelayed(runnableCaptor.capture(),
-                eq(InternetDialog.PROGRESS_DELAY_MS));
-        runnableCaptor.getValue().run();
-
-        // Then hide progress bar
-        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
-    }
-
-    @Test
-    public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() {
-        Mockito.reset(mHandler);
-        when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-        mInternetDialog.mConnectedWifiEntry = null;
-        mInternetDialog.mWifiEntriesCount = 0;
-
-        mInternetDialog.showProgressBar();
-
-        // Show progress bar
-        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
-
-        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).postDelayed(runnableCaptor.capture(),
-                eq(InternetDialog.PROGRESS_DELAY_MS));
-        runnableCaptor.getValue().run();
-
-        // Then hide searching sub-title only
-        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
-        assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 657f912..e572dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -23,6 +23,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -37,8 +39,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import android.annotation.IdRes;
 import android.content.ContentResolver;
 import android.content.res.Configuration;
@@ -86,6 +86,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -402,6 +403,10 @@
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
                 StateFlowKt.MutableStateFlow(false));
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 new FakeDeviceProvisioningRepository(),
@@ -418,7 +423,9 @@
                         new SharedNotificationContainerInteractor(
                                 new FakeConfigurationRepository(),
                                 mContext,
-                                new ResourcesSplitShadeStateController()
+                                new ResourcesSplitShadeStateController(),
+                                mKeyguardInteractor,
+                                deviceEntryUdfpsInteractor
                         ),
                         mShadeRepository
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 39739e7..9d8b214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -23,6 +23,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -54,6 +56,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -75,6 +78,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -138,6 +142,7 @@
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private UserTracker mUserTracker;
+    @Mock private SceneContainerFlags mSceneContainerFlags;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
@@ -233,6 +238,11 @@
                 mKeyguardSecurityModel,
                 mSelectedUserInteractor,
                 powerInteractor);
+
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 new FakeDeviceProvisioningRepository(),
@@ -249,7 +259,9 @@
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
-                                new ResourcesSplitShadeStateController()),
+                                new ResourcesSplitShadeStateController(),
+                                keyguardInteractor,
+                                deviceEntryUdfpsInteractor),
                         shadeRepository
                 )
         );
@@ -274,7 +286,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker) {
+                mUserTracker,
+                mSceneContainerFlags) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e723d7d..eb5633b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.content.res.Resources;
@@ -41,6 +42,7 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.flags.FeatureFlags;
@@ -275,6 +277,10 @@
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
 
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 deviceProvisioningRepository,
@@ -291,7 +297,9 @@
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
-                                splitShadeStateController),
+                                splitShadeStateController,
+                                keyguardInteractor,
+                                deviceEntryUdfpsInteractor),
                         mShadeRepository
                 )
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index a3cff87e6..76c4015 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -30,8 +30,6 @@
 
 import android.app.ActivityManager;
 import android.app.Notification;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -45,12 +43,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
-import com.android.systemui.util.time.SystemClockImpl;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,27 +71,24 @@
     protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
     // Number of notifications to use in tests requiring multiple notifications
     private static final int TEST_NUM_NOTIFICATIONS = 4;
-    protected static final int TEST_TIMEOUT_TIME = 2_000;
-    protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
-    protected Handler mTestHandler;
     protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
-    protected final SystemClock mSystemClock = new SystemClockImpl();
-    protected boolean mTimedOut = false;
+    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
+    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
 
     @Mock protected ExpandableNotificationRow mRow;
 
     static {
         assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
         assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
         private AlertEntry mLastCreatedEntry;
 
-        private TestableAlertingNotificationManager(Handler handler, SystemClock systemClock) {
-            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler, systemClock);
+        private TestableAlertingNotificationManager(SystemClock systemClock,
+                DelayableExecutor executor) {
+            super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
             mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
@@ -118,7 +113,7 @@
     }
 
     protected AlertingNotificationManager createAlertingNotificationManager() {
-        return new TestableAlertingNotificationManager(mTestHandler, mSystemClock);
+        return new TestableAlertingNotificationManager(mSystemClock, mExecutor);
     }
 
     protected StatusBarNotification createSbn(int id, Notification n) {
@@ -155,35 +150,6 @@
         return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
     }
 
-    protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
-            boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
-        final Boolean[] wasAlerting = {null};
-        final Runnable checkAlerting =
-                () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
-
-        mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
-        TestableLooper.get(this).processMessages(2);
-
-        assertFalse("Test timed out", mTimedOut);
-        if (shouldBeAlerting) {
-            assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
-        } else {
-            assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
-        }
-        assertFalse("Should not still be alerting after processing",
-                anm.isAlerting(entry.getKey()));
-    }
-
-    @Before
-    public void setUp() {
-        mTestHandler = Handler.createAsync(Looper.myLooper());
-    }
-
-    @After
-    public void tearDown() {
-        mTestHandler.removeCallbacksAndMessages(null);
-    }
 
     @Test
     public void testShowNotification_addsEntry() {
@@ -203,9 +169,7 @@
         final NotificationEntry entry = createEntry(/* id = */ 0);
 
         alm.showNotification(entry);
-
-        verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
-                "auto dismiss time");
+        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
 
         assertFalse(alm.isAlerting(entry.getKey()));
     }
@@ -217,10 +181,8 @@
 
         alm.showNotification(entry);
 
-        // Try to remove but defer, since the notification has not been shown long enough.
-        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
-                false /* releaseImmediately */);
-
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ false);
         assertFalse(removedImmediately);
         assertTrue(alm.isAlerting(entry.getKey()));
     }
@@ -232,10 +194,8 @@
 
         alm.showNotification(entry);
 
-        // Remove forcibly with releaseImmediately = true.
-        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
-                true /* releaseImmediately */);
-
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
         assertTrue(removedImmediately);
         assertFalse(alm.isAlerting(entry.getKey()));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 34c7b09..42c7375 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -515,6 +515,29 @@
     }
 
     @Test
+    public void testDevicePolicyDoesNotAllowNotifications_deviceOwnerSetsForUserAll() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, USER_ALL, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
     public void testDevicePolicyDoesNotAllowNotifications_userAll() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index dff91dd..f25ce0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -53,6 +54,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.flow.emptyFlow
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -83,6 +85,7 @@
         FromPrimaryBouncerTransitionInteractor
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var mockDarkAnimator: ObjectAnimator
+    @Mock lateinit var deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor
 
     private lateinit var controller: StatusBarStateControllerImpl
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -164,6 +167,8 @@
                 mock(),
                 powerInteractor
             )
+
+        whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
         shadeInteractor =
             ShadeInteractorImpl(
                 testScope.backgroundScope,
@@ -181,7 +186,9 @@
                     SharedNotificationContainerInteractor(
                         configurationRepository,
                         mContext,
-                        ResourcesSplitShadeStateController()
+                        ResourcesSplitShadeStateController(),
+                        keyguardInteractor,
+                        deviceEntryUdfpsInteractor,
                     ),
                     shadeRepository,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 3fef1d9..5bc75e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -148,6 +148,24 @@
     }
 
     @Test
+    fun onWifiEntriesChanged_reasonIsScanResults_fireWifiScanCallbackFalse() {
+        controller.addAccessPointCallback(callback)
+
+        controller.onWifiEntriesChanged(WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS)
+
+        verify(callback).onWifiScan(false)
+    }
+
+    @Test
+    fun onScanRequested_fireWifiScanCallbackTrue() {
+        controller.addAccessPointCallback(callback)
+
+        controller.onScanRequested()
+
+        verify(callback).onWifiScan(true)
+    }
+
+    @Test
     fun testOnNumSavedNetworksChangedDoesntTriggerCallback() {
         controller.addAccessPointCallback(callback)
         controller.onNumSavedNetworksChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index bfa03ee..8cf64a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -48,7 +48,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -56,6 +55,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogAssertKt;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -76,6 +76,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -129,10 +130,6 @@
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
 
-    private Log.TerribleFailureHandler mOldWtfHandler = null;
-    private Log.TerribleFailure mLastWtf = null;
-    private int mWtfCount = 0;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -1756,20 +1753,19 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
         invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
@@ -1778,20 +1774,20 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1803,26 +1799,30 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason,
         // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown, but WTFs ARE logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2);
+
+        addNotif(0, PACKAGE_2);
+
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
+
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
     @Test
-    public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() {
+    public void testOutOfOrderPromoterInvalidationDoesNotThrowBeforeTooManyRuns() {
         // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
         NotifPromoter promoter = new IdPromoter(47);
         CountingInvalidator invalidator = new CountingInvalidator(promoter);
@@ -1830,22 +1830,22 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the promoter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
 
+        addNotif(0, PACKAGE_1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() {
+    @Test
+    public void testOutOfOrderPromoterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
         NotifPromoter promoter = new IdPromoter(47);
         CountingInvalidator invalidator = new CountingInvalidator(promoter);
@@ -1853,20 +1853,20 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the promoter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1878,20 +1878,21 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the comparator is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
         NotifComparator comparator = new HypeComparator(PACKAGE_1);
@@ -1900,16 +1901,20 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the comparator is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1921,20 +1926,21 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
@@ -1943,59 +1949,22 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
-    }
 
-    private void interceptWtfs() {
-        assertNull(mOldWtfHandler);
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
 
-        mLastWtf = null;
-        mWtfCount = 0;
-
-        mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> {
-            Log.e("ShadeListBuilderTest", "Observed WTF: " + e);
-            mLastWtf = e;
-            mWtfCount++;
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
         });
     }
 
-    private void expectNoWtfs() {
-        assertNull(expectWtfs(0));
-    }
-
-    private Log.TerribleFailure expectWtf() {
-        return expectWtfs(1);
-    }
-
-    private Log.TerribleFailure expectWtfs(int expectedWtfCount) {
-        assertNotNull(mOldWtfHandler);
-
-        Log.setWtfHandler(mOldWtfHandler);
-        mOldWtfHandler = null;
-
-        Log.TerribleFailure wtf = mLastWtf;
-        int wtfCount = mWtfCount;
-
-        mLastWtf = null;
-        mWtfCount = 0;
-
-        assertEquals(expectedWtfCount, wtfCount);
-        return wtf;
-    }
-
     @Test
     public void testStableOrdering() {
         mStabilityManager.setAllowEntryReordering(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
index a07b570..327a07d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -20,57 +20,86 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerInteractorTest : SysuiTestCase() {
-    private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var underTest: SharedNotificationContainerInteractor
-
-    @Before
-    fun setUp() {
-        configurationRepository = FakeConfigurationRepository()
-        underTest =
-            SharedNotificationContainerInteractor(
-                configurationRepository,
-                mContext,
-                ResourcesSplitShadeStateController()
-            )
-    }
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val configurationRepository = kosmos.fakeConfigurationRepository
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val underTest = kosmos.sharedNotificationContainerInteractor
 
     @Test
-    fun validateConfigValues() = runTest {
-        overrideResource(R.bool.config_use_split_notification_shade, true)
-        overrideResource(R.bool.config_use_large_screen_shade_header, false)
-        overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
-        overrideResource(R.dimen.notification_panel_margin_bottom, 10)
-        overrideResource(R.dimen.notification_panel_margin_top, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 0)
-        overrideResource(R.dimen.keyguard_split_shade_top_margin, 55)
+    fun validateConfigValues() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.bool.config_use_large_screen_shade_header, false)
+            overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+            overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+            overrideResource(R.dimen.notification_panel_margin_top, 10)
+            overrideResource(R.dimen.large_screen_shade_header_height, 0)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 55)
 
-        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+            val dimens = collectLastValue(underTest.configurationBasedDimensions)
 
-        configurationRepository.onAnyConfigurationChange()
-        runCurrent()
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
 
-        val lastDimens = dimens()!!
+            val lastDimens = dimens()!!
 
-        assertThat(lastDimens.useSplitShade).isTrue()
-        assertThat(lastDimens.useLargeScreenHeader).isFalse()
-        assertThat(lastDimens.marginHorizontal).isEqualTo(0)
-        assertThat(lastDimens.marginBottom).isGreaterThan(0)
-        assertThat(lastDimens.marginTop).isGreaterThan(0)
-        assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
-        assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55)
-    }
+            assertThat(lastDimens.useSplitShade).isTrue()
+            assertThat(lastDimens.useLargeScreenHeader).isFalse()
+            assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+            assertThat(lastDimens.marginBottom).isGreaterThan(0)
+            assertThat(lastDimens.marginTop).isGreaterThan(0)
+            assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+            assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsTrueWithUdfps() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = true
+            fingerprintPropertyRepository.supportsUdfps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(true)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsTrueWithRearFpsAndNoAmbientIndicationArea() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = false
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(true)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsFalseWithRearFpsAndAmbientIndicationArea() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = true
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(false)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index f0205b3..36a4712 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -332,8 +332,8 @@
     fun maxNotificationsOnLockscreen() =
         testScope.runTest {
             var notificationCount = 10
-            val maxNotifications by
-                collectLastValue(underTest.getMaxNotifications { notificationCount })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             showLockscreen()
 
@@ -355,8 +355,8 @@
     fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
         testScope.runTest {
             var notificationCount = 10
-            val maxNotifications by
-                collectLastValue(underTest.getMaxNotifications { notificationCount })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             showLockscreen()
 
@@ -390,7 +390,8 @@
     @Test
     fun maxNotificationsOnShade() =
         testScope.runTest {
-            val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             // Show lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 37ee322..e1bd89f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -25,7 +26,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -45,11 +45,11 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -88,9 +88,9 @@
                 StatusBarStateController statusBarStateController,
                 KeyguardBypassController keyguardBypassController,
                 ConfigurationController configurationController,
-                Handler handler,
                 GlobalSettings globalSettings,
                 SystemClock systemClock,
+                DelayableExecutor executor,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
@@ -104,9 +104,10 @@
                     groupManager,
                     visualStabilityProvider,
                     configurationController,
-                    handler,
+                    mockExecutorHandler(executor),
                     globalSettings,
                     systemClock,
+                    executor,
                     accessibilityManagerWrapper,
                     uiEventLogger,
                     javaAdapter,
@@ -126,9 +127,9 @@
                 mStatusBarStateController,
                 mBypassController,
                 mConfigurationController,
-                mTestHandler,
                 mGlobalSettings,
                 mSystemClock,
+                mExecutor,
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
                 mJavaAdapter,
@@ -142,7 +143,6 @@
     }
 
     @Before
-    @Override
     public void setUp() {
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
         final AccessibilityManagerWrapper accessibilityMgr =
@@ -153,14 +153,6 @@
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
         mContext.getOrCreateTestableResources().addOverride(
                 R.integer.ambient_notification_extension_time, 500);
-
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void tearDown() {
-        super.tearDown();
     }
 
     @Test
@@ -224,8 +216,8 @@
 
         hmp.showNotification(entry);
         hmp.extendHeadsUp();
+        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
 
-        final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
-        verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
+        assertTrue(hmp.isAlerting(entry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 5c56246..84b2c4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
@@ -31,6 +32,7 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertTrue
@@ -39,7 +41,6 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -556,7 +557,7 @@
     fun testDisplayChanged_returnsUpdatedInsets() {
         // GIVEN: get insets on the first display and switch to the second display
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-            mock(DumpManager::class.java))
+            mock<DumpManager>(), mock<CommandRegistry>())
 
         configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -575,7 +576,7 @@
         // GIVEN: get insets on the first display, switch to the second display,
         // get insets and switch back
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-            mock(DumpManager::class.java))
+            mock<DumpManager>(), mock<CommandRegistry>())
 
         configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsetsFirstCall = provider
@@ -601,7 +602,7 @@
         configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
         configurationController.onConfigurationChanged(configuration)
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock(DumpManager::class.java))
+                mock<DumpManager>(), mock<CommandRegistry>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
@@ -623,7 +624,7 @@
     fun onDensityOrFontScaleChanged_listenerNotified() {
         configuration.densityDpi = 12
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock(DumpManager::class.java))
+                mock<DumpManager>(), mock<CommandRegistry>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
@@ -644,7 +645,7 @@
     @Test
     fun onThemeChanged_listenerNotified() {
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock(DumpManager::class.java))
+                mock<DumpManager>(), mock<CommandRegistry>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 2940c39..4c893e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,6 +19,7 @@
 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
 
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,7 +42,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Region;
-import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -56,11 +56,10 @@
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,7 +76,6 @@
 
     private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
-    private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@@ -87,25 +85,18 @@
         assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
         assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
         assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
-
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
-                TEST_A11Y_TIMEOUT_TIME);
     }
 
     private final class TestableHeadsUpManager extends BaseHeadsUpManager {
         TestableHeadsUpManager(Context context,
                 HeadsUpManagerLogger logger,
-                Handler handler,
+                DelayableExecutor executor,
                 GlobalSettings globalSettings,
                 SystemClock systemClock,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
-            super(context, logger, handler, globalSettings, systemClock,
-                    accessibilityManagerWrapper, uiEventLogger);
+            super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
+                    executor, accessibilityManagerWrapper, uiEventLogger);
             mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -183,7 +174,7 @@
     }
 
     private BaseHeadsUpManager createHeadsUpManager() {
-        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mGlobalSettings,
+        return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
                 mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
     }
 
@@ -234,18 +225,6 @@
     }
 
 
-    @Before
-    @Override
-    public void setUp() {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void tearDown() {
-        super.tearDown();
-    }
-
     @Test
     public void testHunRemovedLogging() {
         final BaseHeadsUpManager hum = createHeadsUpManager();
@@ -305,10 +284,9 @@
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
 
-        final int pastJustAutoDismissMillis =
-                TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
-        verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+        assertTrue(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -319,10 +297,10 @@
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
-        verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+        assertFalse(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -333,10 +311,10 @@
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
 
-        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
-        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+        assertTrue(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -347,18 +325,9 @@
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
 
-        final int pastLongestAutoDismissMillis =
-                TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
-        final Boolean[] wasAlerting = {null};
-        final Runnable checkAlerting =
-                () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
-        mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
-        TestableLooper.get(this).processMessages(1);
-
-        assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
-        assertTrue("Should still be alerting after processing",
-                hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -369,10 +338,10 @@
         useAccessibilityTimeout(true);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
-        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+        assertTrue(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -383,10 +352,10 @@
         useAccessibilityTimeout(true);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
-        verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+        assertTrue(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -398,18 +367,14 @@
 
         hum.showNotification(entry);
 
-        // Try to remove but defer, since the notification has not been shown long enough.
         final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), false /* releaseImmediately */);
+                entry.getKey(), /* releaseImmediately = */ false);
+        assertFalse(removedImmediately);
+        assertTrue(hum.isAlerting(entry.getKey()));
 
-        assertFalse("HUN should not be removed before minimum display time", removedImmediately);
-        assertTrue("HUN should still be alerting before minimum display time",
-                hum.isAlerting(entry.getKey()));
+        mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        final int pastMinimumDisplayTimeMillis =
-                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
-        verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
-                "minimum display time");
+        assertFalse(hum.isAlerting(entry.getKey()));
     }
 
 
@@ -420,32 +385,13 @@
         useAccessibilityTimeout(false);
 
         hum.showNotification(entry);
+        mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        // After the minimum display time:
-        // 1. Check whether the notification is still alerting.
-        // 2. Try to remove it and check whether the remove succeeded.
-        // 3. Check whether it is still alerting after trying to remove it.
-        final Boolean[] livedPastMinimumDisplayTime = {null};
-        final Boolean[] removedAfterMinimumDisplayTime = {null};
-        final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
-        final Runnable pastMinimumDisplayTimeRunnable = () -> {
-            livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
-            removedAfterMinimumDisplayTime[0] = hum.removeNotification(
-                    entry.getKey(), /* releaseImmediately = */ false);
-            livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
-        };
-        final int pastMinimumDisplayTimeMillis =
-                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
-        mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
-        // Wait until the minimum display time has passed before attempting removal.
-        TestableLooper.get(this).processMessages(1);
+        assertTrue(hum.isAlerting(entry.getKey()));
 
-        assertTrue("HUN should live past minimum display time",
-                livedPastMinimumDisplayTime[0]);
-        assertTrue("HUN should be removed immediately past minimum display time",
-                removedAfterMinimumDisplayTime[0]);
-        assertFalse("HUN should not live after being removed past minimum display time",
-                livedPastRemoveAfterMinimumDisplayTime[0]);
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ false);
+        assertTrue(removedImmediately);
         assertFalse(hum.isAlerting(entry.getKey()));
     }
 
@@ -457,10 +403,8 @@
 
         hum.showNotification(entry);
 
-        // Remove forcibly with releaseImmediately = true.
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ true);
-
         assertTrue(removedImmediately);
         assertFalse(hum.isAlerting(entry.getKey()));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 01dad38..479309c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -153,6 +153,36 @@
     }
 
     @Test
+    public void testCanSkipLockScreen_updateCalledOnFacesCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFacesCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
+    public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFingerprintsCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
     public void testIsUnlocked() {
         // Is unlocked whenever the keyguard is not showing
         assertThat(mKeyguardStateController.isShowing()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 8585d46..b217195 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -29,6 +29,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -97,6 +99,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -119,6 +122,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -343,6 +347,8 @@
     private Icon mAppBubbleIcon;
     @Mock
     private Display mDefaultDisplay;
+    @Mock
+    private SceneContainerFlags mSceneContainerFlags;
 
     private final SceneTestUtils mUtils = new SceneTestUtils(this);
     private final TestScope mTestScope = mUtils.getTestScope();
@@ -462,6 +468,10 @@
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
 
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor =
                 new ShadeInteractorImpl(
                         mTestScope.getBackgroundScope(),
@@ -478,7 +488,9 @@
                                 new SharedNotificationContainerInteractor(
                                         configurationRepository,
                                         mContext,
-                                        splitShadeStateController),
+                                        splitShadeStateController,
+                                        keyguardInteractor,
+                                        deviceEntryUdfpsInteractor),
                                 shadeRepository
                         )
                 );
@@ -503,7 +515,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker
+                mUserTracker,
+                mSceneContainerFlags
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
new file mode 100644
index 0000000..b88f302
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+
+/**
+ * Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the
+ * same instance
+ */
+class TestStubDrawable : Drawable() {
+
+    override fun draw(canvas: Canvas) = Unit
+    override fun setAlpha(alpha: Int) = Unit
+    override fun setColorFilter(colorFilter: ColorFilter?) = Unit
+    override fun getOpacity(): Int = PixelFormat.UNKNOWN
+
+    override fun equals(other: Any?): Boolean = this === other
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 2cb17b5..c85c27e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -1,19 +1,47 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.dagger.qualifiers.Background
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.TestScope
 
 /** Fake implementation of [CommunalRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
+    @Background applicationScope: CoroutineScope = TestScope(),
     override var isCommunalEnabled: Boolean = false,
     override val desiredScene: MutableStateFlow<CommunalSceneKey> =
-        MutableStateFlow(CommunalSceneKey.Blank)
+        MutableStateFlow(CommunalSceneKey.DEFAULT),
 ) : CommunalRepository {
     override fun setDesiredScene(desiredScene: CommunalSceneKey) {
         this.desiredScene.value = desiredScene
     }
 
+    private val defaultTransitionState =
+        ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)
+    private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null)
+    override val transitionState: StateFlow<ObservableCommunalTransitionState> =
+        _transitionState
+            .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = defaultTransitionState,
+            )
+
+    override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
+        _transitionState.value = transitionState
+    }
+
     fun setIsCommunalEnabled(value: Boolean) {
         isCommunalEnabled = value
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index e289083..a1b6587 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -31,7 +31,6 @@
 
 @SysUISingleton
 class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
-
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
     private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 0e7c662..c5d745a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -121,6 +121,8 @@
 
     override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
 
+    override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 10f9346..6ccb3bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -59,6 +59,17 @@
 ): TerribleFailureLog =
     assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
 
+fun assertLogsWtfs(
+    message: String = "Expected Log.wtf to be called once or more",
+    loggingBlock: () -> Unit,
+): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock)
+
+@JvmOverloads
+fun assertLogsWtfs(
+    message: String = "Expected Log.wtf to be called once or more",
+    loggingRunnable: Runnable,
+): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() }
+
 /** The data passed to [TerribleFailureHandler.onTerribleFailure] */
 data class TerribleFailureLog(
     val tag: String,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 1cb2587..6332c1a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -19,9 +19,14 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 
 val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
     Kosmos.Fixture { InstanceIdSequenceFake(0) }
 val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
 val Kosmos.qsEventLogger: QsEventLoggerFake by
     Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
+
+var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
+var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt
new file mode 100644
index 0000000..f01e3aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.customTileStatePersister: CustomTileStatePersister by
+    Kosmos.Fixture { fakeCustomTileStatePersister }
+val Kosmos.fakeCustomTileStatePersister by Kosmos.Fixture { FakeCustomTileStatePersister() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
new file mode 100644
index 0000000..f8ce707
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+/** Returns mocks */
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+    Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt
new file mode 100644
index 0000000..d93dd8d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.model
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
+
+val Kosmos.workTileRestoreProcessor by Kosmos.Fixture { WorkTileRestoreProcessor() }
+
+var Kosmos.restoreProcessors by
+    Kosmos.Fixture {
+        setOf(
+            workTileRestoreProcessor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
new file mode 100644
index 0000000..0091482
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() }
+var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository }
+
+val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() }
+var Kosmos.autoAddRepository: AutoAddRepository by Kosmos.Fixture { fakeAutoAddRepository }
+
+val Kosmos.fakeRestoreRepository by Kosmos.Fixture { FakeQSSettingsRestoredRepository() }
+var Kosmos.restoreRepository: QSSettingsRestoredRepository by
+    Kosmos.Fixture { fakeRestoreRepository }
+
+val Kosmos.fakeInstalledTilesRepository by
+    Kosmos.Fixture { FakeInstalledTilesComponentRepository() }
+var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by
+    Kosmos.Fixture { fakeInstalledTilesRepository }
+
+val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() }
+var Kosmos.customTileAddedRepository: CustomTileAddedRepository by
+    Kosmos.Fixture { fakeCustomTileAddedRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt
new file mode 100644
index 0000000..35f178b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor
+import com.android.systemui.settings.userTracker
+
+val Kosmos.workTileAutoAddable by
+    Kosmos.Fixture { WorkTileAutoAddable(userTracker, workTileRestoreProcessor) }
+
+var Kosmos.autoAddables by Kosmos.Fixture { setOf(workTileAutoAddable) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
index ebdd6fd..bf8f4da 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -39,14 +39,18 @@
         return getFlow(userId).asStateFlow().filterNotNull()
     }
 
-    suspend fun sendRemoveSignal(userId: Int) {
+    fun sendRemoveSignal(userId: Int) {
         getFlow(userId).value = AutoAddSignal.Remove(spec)
     }
 
-    suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+    fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
         getFlow(userId).value = AutoAddSignal.Add(spec, position)
     }
 
+    fun sendRemoveTrackingSignal(userId: Int) {
+        getFlow(userId).value = AutoAddSignal.RemoveTracking(spec)
+    }
+
     override val description: String
         get() = "FakeAutoAddable($spec)"
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt
new file mode 100644
index 0000000..5e8471c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.pipeline.data.repository.autoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.autoAddables
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+
+val Kosmos.autoAddInteractor by
+    Kosmos.Fixture {
+        AutoAddInteractor(
+            autoAddables,
+            autoAddRepository,
+            dumpManager,
+            qsLogger,
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
new file mode 100644
index 0000000..67df563
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.external.customTileStatePersister
+import com.android.systemui.qs.external.tileLifecycleManagerFactory
+import com.android.systemui.qs.newQSTileFactory
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.userTracker
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.currentTilesInteractor: CurrentTilesInteractor by
+    Kosmos.Fixture {
+        CurrentTilesInteractorImpl(
+            tileSpecRepository,
+            installedTilesRepository,
+            userRepository,
+            customTileStatePersister,
+            { newQSTileFactory },
+            qsTileFactory,
+            customTileAddedRepository,
+            tileLifecycleManagerFactory,
+            userTracker,
+            testDispatcher,
+            testDispatcher,
+            applicationCoroutineScope,
+            qsLogger,
+            pipelineFlagsRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt
new file mode 100644
index 0000000..55c23d4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.pipeline.data.model.restoreProcessors
+import com.android.systemui.qs.pipeline.data.repository.autoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.restoreRepository
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+
+val Kosmos.restoreReconciliationInteractor by
+    Kosmos.Fixture {
+        RestoreReconciliationInteractor(
+            tileSpecRepository,
+            autoAddRepository,
+            restoreRepository,
+            restoreProcessors,
+            qsLogger,
+            applicationCoroutineScope,
+            testDispatcher,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt
new file mode 100644
index 0000000..961545a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.pipelineFlagsRepository by Kosmos.Fixture { QSPipelineFlagsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
new file mode 100644
index 0000000..7d52f5d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared.logging
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+/** mock */
+var Kosmos.qsLogger: QSPipelineLogger by Kosmos.Fixture { mock<QSPipelineLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
new file mode 100644
index 0000000..e9a394a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsDataSaverTileConfig by
+    Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 7494ccf..2ca338a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -72,6 +72,7 @@
         onBeforeUserSwitching()
         onUserChanging()
         onUserChanged()
+        onProfileChanged()
     }
 
     fun onBeforeUserSwitching(userId: Int = _userId) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt
new file mode 100644
index 0000000..ffa86ff
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserTracker by Kosmos.Fixture { FakeUserTracker() }
+var Kosmos.userTracker: UserTracker by Kosmos.Fixture { fakeUserTracker }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
index c8013ef..862e52d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
@@ -10,12 +10,12 @@
 
     override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled
 
-    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
         MutableStateFlow(emptyList())
-    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _lockscreenSmartspaceTargets
+    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _communalSmartspaceTargets
 
-    fun setLockscreenSmartspaceTargets(targets: List<SmartspaceTarget>) {
-        _lockscreenSmartspaceTargets.value = targets
+    fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) {
+        _communalSmartspaceTargets.value = targets
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 3403227..13d577b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -18,6 +18,8 @@
 
 import android.content.applicationContext
 import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
@@ -27,5 +29,7 @@
             configurationRepository = configurationRepository,
             context = applicationContext,
             splitShadeStateController = splitShadeStateController,
+            keyguardInteractor = keyguardInteractor,
+            deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
index 886722e..bade848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -19,19 +19,38 @@
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
 
+    private boolean mIsEnabled = false;
+    private List<Listener> mListeners = new ArrayList<>();
+
     public FakeDataSaverController(LeakCheck test) {
         super(test, "datasaver");
     }
 
     @Override
     public boolean isDataSaverEnabled() {
-        return false;
+        return mIsEnabled;
     }
 
     @Override
     public void setDataSaverEnabled(boolean enabled) {
+        mIsEnabled = enabled;
+        for (Listener listener: mListeners) {
+            listener.onDataSaverChanged(enabled);
+        }
+    }
 
+    @Override
+    public void addCallback(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(Listener listener) {
+        mListeners.remove(listener);
     }
 }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index b315f4a..7fcef9c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -76,6 +76,7 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -94,6 +95,7 @@
 import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType;
 import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
 import androidx.camera.extensions.impl.ProcessResultImpl;
+import androidx.camera.extensions.impl.ProcessorImpl;
 import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
 import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl;
@@ -101,6 +103,7 @@
 import androidx.camera.extensions.impl.advanced.BokehAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl;
 import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl;
+import androidx.camera.extensions.impl.advanced.EyesFreeVideographyAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.ImageProcessorImpl;
 import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl;
@@ -112,6 +115,8 @@
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
 import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -135,22 +140,28 @@
     private static final String RESULTS_VERSION_PREFIX = "1.3";
     // Support for various latency improvements
     private static final String LATENCY_VERSION_PREFIX = "1.4";
-    private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
-            ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
-    private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
-            RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
+    private static final String EFV_VERSION_PREFIX = "1.5";
+    private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
+            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+    private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
+            LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1",
+            NON_INIT_VERSION_PREFIX};
     private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
     private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
             (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
     private static final boolean ESTIMATED_LATENCY_API_SUPPORTED = checkForLatencyAPI();
     private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT &&
-            (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
+            (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
+                    (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)));
+    private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT &&
+            (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
     private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
     private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
     private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) ||
-            EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
+            EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
+            EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
 
     private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private CameraManager mCameraManager;
@@ -509,6 +520,167 @@
      */
     public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
             int extensionType) {
+        if (Flags.concertMode()) {
+            if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
+                // Basic extensions are deprecated starting with extension version 1.5
+                return new Pair<>(new PreviewExtenderImpl() {
+                    @Override
+                    public boolean isExtensionAvailable(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) {
+                        return false;
+                    }
+
+                    @Override
+                    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() {
+                        return null;
+                    }
+
+                    @Override
+                    public ProcessorType getProcessorType() {
+                        return null;
+                    }
+
+                    @Override
+                    public ProcessorImpl getProcessor() {
+                        return null;
+                    }
+
+                    @Nullable
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+                        return null;
+                    }
+
+                    @Override
+                    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+                            Context context) { }
+
+                    @Override
+                    public void onDeInit() { }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public int onSessionType() {
+                        return 0;
+                    }
+                }, new ImageCaptureExtenderImpl() {
+                    @Override
+                    public boolean isExtensionAvailable(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) {
+                        return false;
+                    }
+
+                    @Override
+                    public void init(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) { }
+
+                    @Override
+                    public CaptureProcessorImpl getCaptureProcessor() {
+                        return null;
+                    }
+
+                    @Override
+                    public
+                    List<androidx.camera.extensions.impl.CaptureStageImpl> getCaptureStages() {
+                        return null;
+                    }
+
+                    @Override
+                    public int getMaxCaptureStage() {
+                        return 0;
+                    }
+
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+                        return null;
+                    }
+
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(
+                            Size captureSize) {
+                        return null;
+                    }
+
+                    @Override
+                    public Range<Long> getEstimatedCaptureLatencyRange(
+                            Size captureOutputSize) {
+                        return null;
+                    }
+
+                    @Override
+                    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+                        return null;
+                    }
+
+                    @Override
+                    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isCaptureProcessProgressAvailable() {
+                        return false;
+                    }
+
+                    @Override
+                    public Pair<Long, Long> getRealtimeCaptureLatency() {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isPostviewAvailable() {
+                        return false;
+                    }
+
+                    @Override
+                    public void onInit(String cameraId,
+                            CameraCharacteristics cameraCharacteristics, Context context) { }
+
+                    @Override
+                    public void onDeInit() { }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public int onSessionType() {
+                        return 0;
+                    }
+                });
+            }
+        }
+
         switch (extensionType) {
             case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
                 return new Pair<>(new AutoPreviewExtenderImpl(),
@@ -533,6 +705,82 @@
      * @hide
      */
     public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
+        if (Flags.concertMode()) {
+            if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
+                if (EFV_SUPPORTED) {
+                    return new EyesFreeVideographyAdvancedExtenderImpl();
+                } else {
+                    return new AdvancedExtenderImpl() {
+                        @Override
+                        public boolean isExtensionAvailable(String cameraId,
+                                Map<String, CameraCharacteristics> characteristicsMap) {
+                            return false;
+                        }
+
+                        @Override
+                        public void init(String cameraId,
+                                Map<String, CameraCharacteristics> characteristicsMap) {
+
+                        }
+
+                        @Override
+                        public Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
+                                Size captureOutputSize, int imageFormat) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+                                String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+                                String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+                                Size captureSize) {
+                            return null;
+                        }
+
+                        @Override
+                        public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public SessionProcessorImpl createSessionProcessor() {
+                            return null;
+                        }
+
+                        @Override
+                        public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+                            return null;
+                        }
+
+                        @Override
+                        public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+                            return null;
+                        }
+
+                        @Override
+                        public boolean isCaptureProcessProgressAvailable() {
+                            return false;
+                        }
+
+                        @Override
+                        public boolean isPostviewAvailable() {
+                            return false;
+                        }
+                    };
+                }
+            }
+        }
+
         switch (extensionType) {
             case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
                 return new AutoAdvancedExtenderImpl();
diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md
index 30e899c..d84cb67 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -71,3 +71,24 @@
 The “replace” strategy described above is quite powerful, and can be used in creative ways to sidestep tricky underlying dependencies that aren’t ready yet.
 
 For example, consider a constructor or static initializer that relies on unsupported functionality from another team.  By factoring the unsupported logic into a dedicated method, that method can then be replaced under Ravenwood to offer baseline functionality.
+
+## Strategies for JNI
+
+At the moment, JNI isn't yet supported under Ravenwood, but you may still want to support APIs that are partially implemented with JNI.  The current approach is to use the “replace” strategy to offer a pure-Java alternative implementation for any JNI-provided logic.
+
+Since this approach requires potentially complex re-implementation, it should only be considered for core infrastructure that is critical to unblocking widespread testing use-cases.  Other less-common usages of JNI should instead wait for offical JNI support in the Ravenwood environment.
+
+When a pure-Java implementation grows too large or complex to host within the original class, the `@RavenwoodNativeSubstitutionClass` annotation can be used to host it in a separate source file:
+
+```
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.MyComplexClass_host")
+public class MyComplexClass {
+    private static native void nativeDoThing(long nativePtr);
+...
+
+public class MyComplexClass_host {
+    public static void nativeDoThing(long nativePtr) {
+        // ...
+    }
+```
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d175713..513c095 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,8 @@
 
 package android.platform.test.ravenwood;
 
+import static org.junit.Assert.fail;
+
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -36,6 +38,15 @@
 
     private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
 
+    /**
+     * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect
+     * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
+     *
+     * This is typically helpful for internal maintainers discovering tests that had previously
+     * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
+     */
+    private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
+
     private static final int SYSTEM_UID = 1000;
     private static final int NOBODY_UID = 9999;
     private static final int FIRST_APPLICATION_UID = 10000;
@@ -97,26 +108,76 @@
         return IS_UNDER_RAVENWOOD;
     }
 
+    /**
+     * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
+     * annotation, either at the method or class level.
+     */
+    private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
+        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return true;
+        } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
+        if (ENABLE_PROBE_IGNORED) {
+            return applyProbeIgnored(base, description);
+        } else {
+            return applyDefault(base, description);
+        }
+    }
+
+    /**
+     * Run the given {@link Statement} with no special treatment.
+     */
+    private Statement applyDefault(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                if (hasIgnoreUnderRavenwoodAnnotation(description)) {
                     Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                 }
-                if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                }
-                if (IS_UNDER_RAVENWOOD) {
-                    RavenwoodRuleImpl.init(RavenwoodRule.this);
-                }
+
+                RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
                     base.evaluate();
                 } finally {
-                    if (IS_UNDER_RAVENWOOD) {
-                        RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                }
+            }
+        };
+    }
+
+    /**
+     * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
+     * run under Ravenwood to detect cases where a test is able to pass despite being marked as
+     * {@code IgnoreUnderRavenwood}.
+     */
+    private Statement applyProbeIgnored(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                RavenwoodRuleImpl.init(RavenwoodRule.this);
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    if (hasIgnoreUnderRavenwoodAnnotation(description)) {
+                        // This failure is expected, so eat the exception and report the
+                        // assumption failure that test authors expect
+                        Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                     }
+                    throw t;
+                } finally {
+                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                }
+
+                if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
+                    fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
+                            + "passed under Ravenwood; consider removing the annotation");
                 }
             }
         };
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index fb71e9d..0ff6a1a 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -22,12 +22,10 @@
     }
 
     public static void init(RavenwoodRule rule) {
-        // Must be provided by impl to reference runtime internals
-        throw new UnsupportedOperationException();
+        // No-op when running on a real device
     }
 
     public static void reset(RavenwoodRule rule) {
-        // Must be provided by impl to reference runtime internals
-        throw new UnsupportedOperationException();
+        // No-op when running on a real device
     }
 }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e3f1932..13908f1 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,16 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+com.android.internal.os.BatteryStatsHistory
+com.android.internal.os.BatteryStatsHistory$TraceDelegate
+com.android.internal.os.BatteryStatsHistory$VarintParceler
+com.android.internal.os.BatteryStatsHistoryIterator
+com.android.internal.os.Clock
+com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
+com.android.internal.os.MonotonicClock
+com.android.internal.os.PowerStats
+com.android.internal.os.PowerStats$Descriptor
 
 android.util.AtomicFile
 android.util.DataUnit
@@ -22,8 +32,16 @@
 android.util.TimeUtils
 android.util.Xml
 
+android.os.BatteryConsumer
+android.os.BatteryStats$HistoryItem
+android.os.BatteryStats$HistoryStepDetails
+android.os.BatteryStats$HistoryTag
+android.os.BatteryStats$ProcessStateChange
 android.os.Binder
 android.os.Binder$IdentitySupplier
+android.os.Broadcaster
+android.os.BundleMerger
+android.os.ConditionVariable
 android.os.FileUtils
 android.os.FileUtils$MemoryPipe
 android.os.Handler
@@ -33,12 +51,15 @@
 android.os.Looper
 android.os.Message
 android.os.MessageQueue
+android.os.PackageTagsList
 android.os.Parcel
 android.os.Parcelable
 android.os.Process
 android.os.SystemClock
 android.os.ThreadLocalWorkSource
+android.os.TimestampedValue
 android.os.UserHandle
+android.os.WorkSource
 
 android.content.ClipData
 android.content.ClipData$Item
@@ -92,8 +113,6 @@
 
 com.android.server.LocalServices
 
-com.android.internal.os.SomeArgs
-
 com.android.internal.util.BitUtils
 com.android.internal.util.BitwiseInputStream
 com.android.internal.util.BitwiseOutputStream
@@ -115,6 +134,23 @@
 com.android.internal.util.RingBuffer
 com.android.internal.util.StringPool
 
+com.android.internal.os.BinderCallHeavyHitterWatcher
+com.android.internal.os.BinderDeathDispatcher
+com.android.internal.os.BinderfsStatsReader
+com.android.internal.os.BinderLatencyBuckets
+com.android.internal.os.CachedDeviceState
+com.android.internal.os.Clock
+com.android.internal.os.CpuScalingPolicies
+com.android.internal.os.CpuScalingPolicyReader
+com.android.internal.os.KernelCpuThreadReader
+com.android.internal.os.LoggingPrintStream
+com.android.internal.os.LooperStats
+com.android.internal.os.MonotonicClock
+com.android.internal.os.ProcLocksReader
+com.android.internal.os.ProcStatsUtil
+com.android.internal.os.SomeArgs
+com.android.internal.os.StoragedUidIoStatsReader
+
 com.google.android.collect.Lists
 com.google.android.collect.Maps
 com.google.android.collect.Sets
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index de05777..5df827f 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -74,7 +74,7 @@
 $ atest --host MyTestsRavenwood
 ```
 
-> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until bug #312525698 is fixed.
+> **Note:** There's a known bug #312525698 where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until the bug is fixed.
 
 You can also run your new tests automatically via `TEST_MAPPING` rules like this:
 
@@ -89,6 +89,8 @@
 }
 ```
 
+> **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward.
+
 ## Strategies for feature flags
 
 Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team:
@@ -112,9 +114,9 @@
 
 ## Strategies for migration/bivalent tests
 
-Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment.
+Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment.
 
-In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices.  Please note that your test must declare a `RavenwoodRule` for the annotation to take effect.
+In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices.  The annotation can be applied to either individual methods or to an entire test class.  Please note that your test class must declare a `RavenwoodRule` for the annotation to take effect.
 
 Test authors are encouraged to provide a `blockedBy` or `reason` argument to help future maintainers understand why a test is being ignored, and under what conditions it might be supported in the future.
 
@@ -137,11 +139,39 @@
 }
 ```
 
+At the moment, the `android.content.res.Resources` subsystem isn't yet supported under Ravenwood, but you may still want to dual-compile test suites that depend on references to resources.  Below is a strategy for supporting dual-compiliation, where you can "borrow" the generated resource symbols from your traditional `android_test` target:
+
+```
+android_test {
+    name: "MyTestsDevice",
+    resource_dirs: ["res"],
+...
+
+android_ravenwood_test {
+    name: "MyTestsRavenwood",
+    srcs: [
+        ":MyTestsDevice{.aapt.srcjar}",
+...
+```
+
 ## Strategies for unsupported APIs
 
 As you write tests against Ravenwood, you’ll likely discover API dependencies that aren’t supported yet.  Here’s a few strategies that can help you make progress:
 
-* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling.  (For example, providing a specific `File` argument instead of deriving it internally from a `Context`.)
+* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling.  (For example, providing a specific `File` argument instead of deriving paths internally from a `Context` or `Environment`.)
+    * One common use-case is providing a directory for your test to store temporary files, which can easily be accomplished using the `Files.createTempDirectory()` API which works on both physical devices and under Ravenwood:
+
+```
+import java.nio.file.Files;
+
+@RunWith(AndroidJUnit4.class)
+public class MyTest {
+    @Before
+    public void setUp() throws Exception {
+        File tempDir = Files.createTempDirectory("MyTest").toFile();
+...
+```
+
 * Although mocking code that your team doesn’t own is a generally discouraged testing practice, it can be a valuable pressure relief valve when a dependency isn’t yet supported.
 
 ## Strategies for debugging test development
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0696807..97d36d4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -209,6 +209,7 @@
     final ComponentName mComponentName;
 
     int mGenericMotionEventSources;
+    int mObservedMotionEventSources;
 
     // the events pending events to be dispatched to this service
     final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
@@ -397,6 +398,19 @@
         mNotificationTimeout = info.notificationTimeout;
         mIsDefault = (info.flags & DEFAULT) != 0;
         mGenericMotionEventSources = info.getMotionEventSources();
+        if (android.view.accessibility.Flags.motionEventObserving()) {
+            if (mContext.checkCallingOrSelfPermission(
+                            android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING)
+                    == PackageManager.PERMISSION_GRANTED) {
+                mObservedMotionEventSources = info.getObservedMotionEventSources();
+            } else {
+                Slog.e(
+                        LOG_TAG,
+                        "Observing motion events requires"
+                            + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING.");
+                mObservedMotionEventSources = 0;
+            }
+        }
 
         if (supportsFlagForNotImportantViews(info)) {
             if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
@@ -1599,7 +1613,7 @@
             final int displayId = displays[i].getDisplayId();
             onDisplayRemoved(displayId);
         }
-        if (Flags.cleanupA11yOverlays()) {
+        if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) {
             detachAllOverlays();
         }
     }
@@ -1919,6 +1933,7 @@
         return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
     }
 
+
     /**
      * Called by the invocation handler to notify the service that the
      * state of magnification has changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 6cac6a4..9ddc35a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -198,6 +198,7 @@
     // State tracking for generic MotionEvents is display-agnostic so we only need one.
     private GenericMotionEventStreamState mGenericMotionEventStreamState;
     private int mCombinedGenericMotionEventSources = 0;
+    private int mCombinedMotionEventObservedSources = 0;
 
     private EventStreamState mKeyboardStreamState;
 
@@ -525,16 +526,33 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) {
-            addFirstEventHandler(displayId, new BaseEventStreamTransformation() {
-                @Override
-                public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
-                        int policyFlags) {
-                    if (!anyServiceWantsGenericMotionEvent(rawEvent)
-                            || !mAms.sendMotionEventToListeningServices(rawEvent)) {
-                        super.onMotionEvent(event, rawEvent, policyFlags);
-                    }
-                }
-            });
+            addFirstEventHandler(
+                    displayId,
+                    new BaseEventStreamTransformation() {
+                        @Override
+                        public void onMotionEvent(
+                                MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+                            boolean passAlongEvent = true;
+                            if (anyServiceWantsGenericMotionEvent(event)) {
+                                // Some service wants this event, so try to deliver it to at least
+                                // one service.
+                                if (mAms.sendMotionEventToListeningServices(event)) {
+                                    // A service accepted this event, so prevent it from passing
+                                    // down the stream by default.
+                                    passAlongEvent = false;
+                                }
+                                // However, if a service is observing these events instead of
+                                // consuming them then ensure
+                                // it is always passed along to the next stage of the event stream.
+                                if (anyServiceWantsToObserveMotionEvent(event)) {
+                                    passAlongEvent = true;
+                                }
+                            }
+                            if (passAlongEvent) {
+                                super.onMotionEvent(event, rawEvent, policyFlags);
+                            }
+                        }
+                    });
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
@@ -542,15 +560,14 @@
                 || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0)
                 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
             final MagnificationGestureHandler magnificationGestureHandler =
-                    createMagnificationGestureHandler(displayId,
-                            displayContext);
+                    createMagnificationGestureHandler(displayId, displayContext);
             addFirstEventHandler(displayId, magnificationGestureHandler);
             mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
-            MotionEventInjector injector = new MotionEventInjector(
-                    mContext.getMainLooper(), mAms.getTraceManager());
+            MotionEventInjector injector =
+                    new MotionEventInjector(mContext.getMainLooper(), mAms.getTraceManager());
             addFirstEventHandler(displayId, injector);
             mMotionEventInjectors.put(displayId, injector);
         }
@@ -923,6 +940,20 @@
         }
     }
 
+    private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
+        // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
+        // touch exploration.
+        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+                && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+            return false;
+        }
+        final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+        return (mCombinedGenericMotionEventSources
+                        & mCombinedMotionEventObservedSources
+                        & eventSourceWithoutClass)
+                != 0;
+    }
+
     private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
         // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
         // touch exploration.
@@ -938,6 +969,10 @@
         mCombinedGenericMotionEventSources = sources;
     }
 
+    public void setCombinedMotionEventObservedSources(int sources) {
+        mCombinedMotionEventObservedSources = sources;
+    }
+
     /**
      * Keeps state of streams of events from all keyboard devices.
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 440e996..edb41639 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2825,8 +2825,10 @@
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
             }
             int combinedGenericMotionEventSources = 0;
+            int combinedMotionEventObservedSources = 0;
             for (AccessibilityServiceConnection connection : userState.mBoundServices) {
                 combinedGenericMotionEventSources |= connection.mGenericMotionEventSources;
+                combinedMotionEventObservedSources |= connection.mObservedMotionEventSources;
             }
             if (combinedGenericMotionEventSources != 0) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
@@ -2845,6 +2847,8 @@
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
                 mInputFilter.setCombinedGenericMotionEventSources(
                         combinedGenericMotionEventSources);
+                mInputFilter.setCombinedMotionEventObservedSources(
+                        combinedMotionEventObservedSources);
             } else {
                 if (mHasInputFilter) {
                     mHasInputFilter = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
index 307b555..8471061 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
@@ -224,7 +224,7 @@
         pw.println("            IAccessibilityInteractionConnectionCallback");
         pw.println("            IRemoteMagnificationAnimationCallback");
         pw.println("            IMagnificationConnection");
-        pw.println("            IWindowMagnificationConnectionCallback");
+        pw.println("            IMagnificationConnectionCallback");
         pw.println("            WindowManagerInternal");
         pw.println("            WindowsForAccessibilityCallback");
         pw.println("            MagnificationCallbacks");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 0a2a780..baae1d93 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -861,6 +861,7 @@
     }
 
     final class DetectingStateWithMultiFinger extends DetectingState {
+        private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2;
         // A flag set to true when two fingers have touched down.
         // Used to indicate what next finger action should be.
         private boolean mIsTwoFingerCountReached = false;
@@ -917,7 +918,8 @@
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
 
                     if (event.getPointerCount() == 2) {
-                        if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) {
+                        if (isMultiFingerMultiTapTriggered(
+                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) {
                             // 3tap and hold
                             afterLongTapTimeoutTransitionToDraggingState(event);
                         } else {
@@ -962,7 +964,8 @@
                         // (which is a rare combo to be used aside from magnification)
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
-                        } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)
+                        } else if (isMultiFingerMultiTapTriggered(
+                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)
                                 && event.getPointerCount() == 2) {
                             transitionToViewportDraggingStateAndClear(event);
                         } else if (isActivated() && event.getPointerCount() == 2) {
@@ -1009,7 +1012,7 @@
                             mDisplayId, event.getX(), event.getY())) {
                         transitionToDelegatingStateAndClear();
 
-                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
+                    } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) {
                         // Placing multiple fingers before a single finger, because achieving a
                         // multi finger multi tap also means achieving a single finger triple tap
                         onTripleTap(event);
@@ -1051,7 +1054,7 @@
                 mIsTwoFingerCountReached = false;
             }
 
-            if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+            if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) {
                 final boolean enabled = !isActivated();
                 mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
             }
@@ -1075,7 +1078,7 @@
             // Only log the 3tap and hold event
             if (!shortcutTriggered) {
                 final boolean enabled = !isActivated();
-                if (mCompletedTapCount == 2) {
+                if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) {
                     // Two finger triple tap and hold
                     mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
                 } else {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index eff6488..e6af54b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -17,7 +17,7 @@
 package com.android.server.accessibility.magnification;
 
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
 
 import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID;
@@ -43,7 +43,7 @@
 import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
 import android.view.accessibility.IMagnificationConnection;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.internal.accessibility.common.MagnificationConstants;
@@ -922,16 +922,16 @@
         disableWindowMagnification(displayId, true);
     }
 
-    private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements
+    private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements
             IBinder.DeathRecipient {
         private boolean mExpiredDeathRecipient = false;
 
         @Override
         public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId + ";bounds=" + bounds);
             }
             synchronized (mLock) {
@@ -951,9 +951,9 @@
         public void onChangeMagnificationMode(int displayId, int magnificationMode)
                 throws RemoteException {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId + ";mode=" + magnificationMode);
             }
             mCallback.onChangeMagnificationMode(displayId, magnificationMode);
@@ -962,9 +962,9 @@
         @Override
         public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId + ";source=" + sourceBounds);
             }
             synchronized (mLock) {
@@ -980,9 +980,9 @@
         @Override
         public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId + ";scale=" + scale
                                 + ";updatePersistence=" + updatePersistence);
             }
@@ -992,9 +992,9 @@
         @Override
         public void onAccessibilityActionPerformed(int displayId) {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId);
             }
             mCallback.onAccessibilityActionPerformed(displayId);
@@ -1003,9 +1003,9 @@
         @Override
         public void onMove(int displayId) {
             if (mTrace.isA11yTracingEnabledForTypes(
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                    FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onMove",
-                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                         "displayId=" + displayId);
             }
             setTrackingTypingFocusEnabled(displayId, false);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
index d7098a7..c63784a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
@@ -17,8 +17,8 @@
 package com.android.server.accessibility.magnification;
 
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.os.IBinder.DeathRecipient;
 
 import android.annotation.NonNull;
@@ -26,8 +26,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
@@ -217,13 +217,13 @@
         return true;
     }
 
-    boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
+    boolean setConnectionCallback(IMagnificationConnectionCallback connectionCallback) {
         if (mTrace.isA11yTracingEnabledForTypes(
                 FLAGS_MAGNIFICATION_CONNECTION
-                | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                | FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
             mTrace.logTrace(TAG + ".setConnectionCallback",
                     FLAGS_MAGNIFICATION_CONNECTION
-                    | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                    | FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
                     "callback=" + connectionCallback);
         }
         try {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 73c267a..75d01f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -481,10 +481,10 @@
 
                 if (mDetectTwoFingerTripleTap) {
                     mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2,
-                            /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
+                            /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
                             null));
                     mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2,
-                            /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
+                            /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
                             null));
                 }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 923728f..8dc6537 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -455,7 +455,7 @@
                     cameraAccessController, mPendingTrampolineCallback, activityListener,
                     soundEffectListener, runningAppsChangedCallback, params);
             if (Flags.expressMetrics()) {
-                Counter.logIncrement("virtual_devices.virtual_devices_created_count");
+                Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
             }
 
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 34787a3..145303d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -554,6 +554,10 @@
             if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
             return;
         }
+        if (mRemoteService.getServiceInterface() == null) {
+            if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): remote service is dead or unbound");
+            return;
+        }
         final ActivityEvent event = new ActivityEvent(activityId, componentName, type);
 
         if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b4cf34e..a0ccbf3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -166,7 +166,7 @@
         "android.hardware.health-V1.0-java", // HIDL
         "android.hardware.health-V2.0-java", // HIDL
         "android.hardware.health-V2.1-java", // HIDL
-        "android.hardware.health-V2-java", // AIDL
+        "android.hardware.health-V3-java", // AIDL
         "android.hardware.health-translate-java",
         "android.hardware.light-V1-java",
         "android.hardware.security.rkp-V3-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 31c9348..09e7986 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -222,6 +222,14 @@
             int callingUid);
 
     /**
+     * Like {@link #getInstalledApplications}, but allows the fetching of apps
+     * cross user.
+     */
+    public abstract List<ApplicationInfo> getInstalledApplicationsCrossUser(
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid);
+
+    /**
      * Retrieve launcher extras for a suspended package provided to the system in
      * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
      * PersistableBundle, String)}.
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 708da19..5e9d1cb 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -110,6 +110,9 @@
         },
         {
             "name": "FrameworksNetTests"
+        },
+        {
+            "name": "CtsSuspendAppsTestCases"
         }
     ],
     "presubmit-large": [
@@ -150,9 +153,6 @@
             "name": "CtsPackageManagerTestCases"
         },
         {
-            "name": "CtsSuspendAppsTestCases"
-        },
-        {
             "name": "FrameworksServicesTests",
             "options": [
                 {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 3280afdf..627a62e 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -42,6 +42,7 @@
 import android.debug.AdbNotifications;
 import android.debug.AdbProtoEnums;
 import android.debug.AdbTransportType;
+import android.debug.IAdbTransport;
 import android.debug.PairDevice;
 import android.net.ConnectivityManager;
 import android.net.LocalSocket;
@@ -66,6 +67,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.adb.AdbDebuggingManagerProto;
+import android.text.TextUtils;
 import android.util.AtomicFile;
 import android.util.Base64;
 import android.util.Slog;
@@ -679,16 +681,17 @@
                             return;
                         }
 
-                        // Check for network change
-                        String bssid = wifiInfo.getBSSID();
-                        if (bssid == null || bssid.isEmpty()) {
-                            Slog.e(TAG, "Unable to get the wifi ap's BSSID. Disabling adbwifi.");
-                            Settings.Global.putInt(mContentResolver,
-                                    Settings.Global.ADB_WIFI_ENABLED, 0);
-                            return;
-                        }
                         synchronized (mAdbConnectionInfo) {
-                            if (!bssid.equals(mAdbConnectionInfo.getBSSID())) {
+                            // Check for network change
+                            final String bssid = wifiInfo.getBSSID();
+                            if (TextUtils.isEmpty(bssid)) {
+                                Slog.e(TAG,
+                                        "Unable to get the wifi ap's BSSID. Disabling adbwifi.");
+                                Settings.Global.putInt(mContentResolver,
+                                        Settings.Global.ADB_WIFI_ENABLED, 0);
+                                return;
+                            }
+                            if (!TextUtils.equals(bssid, mAdbConnectionInfo.getBSSID())) {
                                 Slog.i(TAG, "Detected wifi network change. Disabling adbwifi.");
                                 Settings.Global.putInt(mContentResolver,
                                         Settings.Global.ADB_WIFI_ENABLED, 0);
@@ -1397,7 +1400,7 @@
             }
 
             String bssid = wifiInfo.getBSSID();
-            if (bssid == null || bssid.isEmpty()) {
+            if (TextUtils.isEmpty(bssid)) {
                 Slog.e(TAG, "Unable to get the wifi ap's BSSID.");
                 return null;
             }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f8f3d82..ace2cfd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -33,6 +33,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
 import android.annotation.SuppressLint;
+import android.app.AlarmManager;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -427,13 +428,7 @@
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
         mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
                 mStats.getHistory());
-        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
-        final long powerStatsAggregationPeriod = context.getResources().getInteger(
-                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
-        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
-                aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
-                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats);
+        mPowerStatsScheduler = createPowerStatsScheduler(mContext);
         PowerStatsExporter powerStatsExporter =
                 new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
@@ -445,6 +440,23 @@
         mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
     }
 
+    private PowerStatsScheduler createPowerStatsScheduler(Context context) {
+        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+        final long powerStatsAggregationPeriod = context.getResources().getInteger(
+                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+        PowerStatsScheduler.AlarmScheduler alarmScheduler =
+                (triggerAtMillis, tag, onAlarmListener, aHandler) -> {
+                    AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+                    alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, tag,
+                            onAlarmListener, aHandler);
+                };
+        return new PowerStatsScheduler(mStats::schedulePowerStatsSampleCollection,
+                mPowerStatsAggregator, aggregatedPowerStatsSpanDuration,
+                powerStatsAggregationPeriod, mPowerStatsStore, alarmScheduler, Clock.SYSTEM_CLOCK,
+                mMonotonicClock, () -> mStats.getHistory().getStartTime(), mHandler);
+    }
+
     private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 903cb7b..982076d 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,7 +30,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -164,12 +163,6 @@
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
-
-        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
                 TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index caafb42..fc8ad6bc 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -35,6 +35,7 @@
 import android.app.ForegroundServiceDelegationOptions;
 import android.content.ComponentName;
 import android.content.pm.ServiceInfo;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.LongArray;
@@ -134,6 +135,11 @@
      * call of the right type will also be associated and logged
      */
     public void logForegroundServiceStart(int uid, int pid, ServiceRecord record) {
+        if (record.getComponentName() != null) {
+            final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, traceTag,
+                    "foregroundService", record.foregroundServiceType);
+        }
         // initialize the UID stack
         UidState uidState = mUids.get(uid);
         if (uidState == null) {
@@ -205,6 +211,11 @@
         // we need to log all the API end events and remove the start events
         // then we remove the FGS from the various stacks
         // and also clean up the start calls stack by UID
+        if (record.getComponentName() != null) {
+            final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    traceTag, record.hashCode());
+        }
         final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
         if (apiTypes.size() == 0) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b2082d9..b4cd6a3 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -401,7 +401,7 @@
     /**
      * All about the process state info (proc state, oom adj score) in this process.
      */
-    final ProcessStateRecord mState;
+    ProcessStateRecord mState;
 
     /**
      * All about the state info of the optimizer when the process is cached.
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 824bdd4..32d5cf5 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.EXTRA_REPLACING;
+import static android.server.app.Flags.gameDefaultFrameRate;
 
 import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
 import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
@@ -28,6 +29,7 @@
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -66,11 +68,13 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PermissionEnforcer;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
@@ -138,6 +142,10 @@
     static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
     static final int WRITE_DELAY_MILLIS = 10 * 1000;  // 10 seconds
     static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds
+    static final String PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED =
+            "persist.graphics.game_default_frame_rate.enabled";
+    static final String PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE =
+            "ro.surface_flinger.game_default_frame_rate_override";
 
     private static final String PACKAGE_NAME_MSG_KEY = "packageName";
     private static final String USER_ID_MSG_KEY = "userId";
@@ -154,7 +162,6 @@
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
     private final PowerManagerInternal mPowerManagerInternal;
-    private final File mSystemDir;
     @VisibleForTesting
     final AtomicFile mGameModeInterventionListFile;
     private DeviceConfigListener mDeviceConfigListener;
@@ -175,28 +182,57 @@
     final MyUidObserver mUidObserver;
     @GuardedBy("mUidObserverLock")
     private final Set<Integer> mForegroundGameUids = new HashSet<>();
+    private final GameManagerServiceSystemPropertiesWrapper mSysProps;
+    private float mGameDefaultFrameRateValue;
+
+    @VisibleForTesting
+    static class Injector {
+        public GameManagerServiceSystemPropertiesWrapper createSystemPropertiesWrapper() {
+            return new GameManagerServiceSystemPropertiesWrapper() {
+                @Override
+                public String get(String key, String def) {
+                    return SystemProperties.get(key, def);
+                }
+                @Override
+                public boolean getBoolean(String key, boolean def) {
+                    return SystemProperties.getBoolean(key, def);
+                }
+
+                @Override
+                public int getInt(String key, int def) {
+                    return SystemProperties.getInt(key, def);
+                }
+
+                @Override
+                public void set(String key, String val) {
+                    SystemProperties.set(key, val);
+                }
+            };
+        }
+    }
 
     public GameManagerService(Context context) {
         this(context, createServiceThread().getLooper());
     }
 
     GameManagerService(Context context, Looper looper) {
-        this(context, looper, Environment.getDataDirectory());
+        this(context, looper, Environment.getDataDirectory(), new Injector());
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    GameManagerService(Context context, Looper looper, File dataDir) {
+    GameManagerService(Context context, Looper looper, File dataDir, Injector injector) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mHandler = new SettingsHandler(looper);
         mPackageManager = mContext.getPackageManager();
         mUserManager = mContext.getSystemService(UserManager.class);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
-        mSystemDir = new File(dataDir, "system");
-        mSystemDir.mkdirs();
-        FileUtils.setPermissions(mSystemDir.toString(),
+        File systemDir = new File(dataDir, "system");
+        systemDir.mkdirs();
+        FileUtils.setPermissions(systemDir.toString(),
                 FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
                 -1, -1);
-        mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
+        mGameModeInterventionListFile = new AtomicFile(new File(systemDir,
                 GAME_MODE_INTERVENTION_LIST_FILE_NAME));
         FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
                 FileUtils.S_IRUSR | FileUtils.S_IWUSR
@@ -220,6 +256,8 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not register UidObserver");
         }
+
+        mSysProps = injector.createSystemPropertiesWrapper();
     }
 
     @Override
@@ -1521,6 +1559,10 @@
         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
         Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
         mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+
+        mGameDefaultFrameRateValue = (float) mSysProps.getInt(
+                PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 60);
+        Slog.v(TAG, "Game Default Frame Rate : " + mGameDefaultFrameRateValue);
     }
 
     private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
@@ -1588,7 +1630,7 @@
         try {
             final float fps = 0.0f;
             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
-            setOverrideFrameRate(uid, fps);
+            setGameModeFrameRateOverride(uid, fps);
         } catch (PackageManager.NameNotFoundException e) {
             return;
         }
@@ -1620,7 +1662,7 @@
         try {
             final float fps = modeConfig.getFps();
             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
-            setOverrideFrameRate(uid, fps);
+            setGameModeFrameRateOverride(uid, fps);
         } catch (PackageManager.NameNotFoundException e) {
             return;
         }
@@ -2159,14 +2201,69 @@
     }
 
     @VisibleForTesting
-    void setOverrideFrameRate(int uid, float frameRate) {
-        nativeSetOverrideFrameRate(uid, frameRate);
+    void setGameModeFrameRateOverride(int uid, float frameRate) {
+        nativeSetGameModeFrameRateOverride(uid, frameRate);
+    }
+
+    @VisibleForTesting
+    void setGameDefaultFrameRateOverride(int uid, float frameRate) {
+        Slog.v(TAG, "setDefaultFrameRateOverride : " + uid + " , " + frameRate);
+        nativeSetGameDefaultFrameRateOverride(uid, frameRate);
+    }
+
+    private float getGameDefaultFrameRate() {
+        final boolean isGameDefaultFrameRateEnabled;
+        float gameDefaultFrameRate = 0.0f;
+        synchronized (mLock) {
+            isGameDefaultFrameRateEnabled =
+                    mSysProps.getBoolean(
+                            PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, true);
+        }
+        if (gameDefaultFrameRate()) {
+            gameDefaultFrameRate = isGameDefaultFrameRateEnabled
+                    ? mGameDefaultFrameRateValue : 0.0f;
+        }
+        return gameDefaultFrameRate;
+    }
+
+    @Override
+    @EnforcePermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void toggleGameDefaultFrameRate(boolean isEnabled) {
+        toggleGameDefaultFrameRate_enforcePermission();
+        if (gameDefaultFrameRate()) {
+            Slog.v(TAG, "toggleGameDefaultFrameRate : " + isEnabled);
+            this.toggleGameDefaultFrameRateUnchecked(isEnabled);
+        }
+    }
+
+    private void toggleGameDefaultFrameRateUnchecked(boolean isEnabled) {
+        // Update system properties.
+        // Here we only need to immediately update games that are in the foreground.
+        // We will update game default frame rate when a game comes into foreground in
+        // MyUidObserver.
+        synchronized (mLock) {
+            if (isEnabled) {
+                mSysProps.set(
+                        PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, "true");
+            } else {
+                mSysProps.set(
+                        PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, "false");
+            }
+        }
+
+        // Update all foreground games' frame rate.
+        synchronized (mUidObserverLock) {
+            for (int uid : mForegroundGameUids) {
+                setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate());
+            }
+        }
     }
 
     /**
      * load dynamic library for frame rate overriding JNI calls
      */
-    private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
+    private static native void nativeSetGameModeFrameRateOverride(int uid, float frameRate);
+    private static native void nativeSetGameDefaultFrameRateOverride(int uid, float frameRate);
 
     final class MyUidObserver extends UidObserver {
         @Override
@@ -2179,6 +2276,7 @@
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             synchronized (mUidObserverLock) {
+
                 if (procState != ActivityManager.PROCESS_STATE_TOP) {
                     disableGameMode(uid);
                     return;
@@ -2198,6 +2296,7 @@
                     Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
                     mPowerManagerInternal.setPowerMode(Mode.GAME, true);
                 }
+                setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate());
                 mForegroundGameUids.add(uid);
             }
         }
diff --git a/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java b/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java
new file mode 100644
index 0000000..afaceda
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemProperties;
+/**
+ * Wrapper interface to access {@link SystemProperties}.
+ *
+ * @hide
+ */
+interface GameManagerServiceSystemPropertiesWrapper {
+    /**
+     * Get the String value for the given {@code key}.
+     *
+     * @param key the key to lookup
+     * @param def the default value in case the property is not set or empty
+     * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
+     * string otherwise
+     */
+    @NonNull
+    String get(@NonNull String key, @Nullable String def);
+    /**
+     * Get the Boolean value for the given {@code key}.
+     *
+     * @param key the key to lookup
+     * @param def the default value in case the property is not set or empty
+     * @return if the {@code key} isn't found, return {@code def} if it isn't null, not parsable
+     * or an empty string otherwise
+     */
+    @NonNull
+    boolean getBoolean(@NonNull String key, boolean def);
+
+    /**
+     * Get the Integer value for the given {@code key}.
+     *
+     * @param key the key to lookup
+     * @param def the default value in case the property is not set or empty
+     * @return if the {@code key} isn't found, return {@code def} if it isn't null, not parsable
+     * or an empty string otherwise
+     */
+    @NonNull
+    int getInt(@NonNull String key, int def);
+    /**
+     * Set the value for the given {@code key} to {@code val}.
+     *
+     * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
+     * @throws RuntimeException if the property cannot be set, for example, if it was blocked by
+     * SELinux. libc will log the underlying reason.
+     */
+    void set(@NonNull String key, @Nullable String val);
+}
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
new file mode 100644
index 0000000..f2e4783
--- /dev/null
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.server.app"
+
+flag {
+    name: "game_default_frame_rate"
+    namespace: "game"
+    description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
+    bug: "286084594"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 44cb136..290bb7e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5106,7 +5106,7 @@
     private void setMasterMuteInternalNoCallerCheck(
             boolean mute, int flags, int userId, String eventSource) {
         if (DEBUG_VOL) {
-            Log.d(TAG, TextUtils.formatSimple("Master mute %s, %d, user=%d from %s",
+            Log.d(TAG, TextUtils.formatSimple("Master mute %s, flags 0x%x, userId=%d from %s",
                     mute, flags, userId, eventSource));
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 3529b04..b1b1dba 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -122,11 +122,10 @@
 
     /**
      * Flag: This flag identifies secondary displays that should show system decorations, such as
-     * status bar, navigation bar, home activity or IME.
+     * navigation bar, home activity or wallpaper.
      * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
      * @hide
      */
-    // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
     public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12;
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 3fc9594..972f857 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3504,6 +3504,13 @@
         wm.addView(view, lp);
     }
 
+    /**
+     * Sets Accessibility bounce keys threshold in milliseconds.
+     */
+    public void setAccessibilityBounceKeysThreshold(int thresholdTimeMs) {
+        mNative.setAccessibilityBounceKeysThreshold(thresholdTimeMs);
+    }
+
     interface KeyboardBacklightControllerInterface {
         default void incrementKeyboardBacklight(int deviceId) {}
         default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 8e0289e..0012eab1 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -85,7 +85,9 @@
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS),
                         (reason) -> updateKeyRepeatInfo()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT),
-                        (reason) -> updateShowRotaryInput()));
+                        (reason) -> updateShowRotaryInput()),
+                Map.entry(Settings.System.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
+                        (reason) -> updateAccessibilityBounceKeys()));
     }
 
     /**
@@ -216,4 +218,9 @@
         }
         mNative.setMaximumObscuringOpacityForTouch(opacity);
     }
+
+    private void updateAccessibilityBounceKeys() {
+        mService.setAccessibilityBounceKeysThreshold(
+                InputSettings.getAccessibilityBounceKeysThreshold(mContext));
+    }
 }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 620cde5..49bbe9a 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -246,6 +246,11 @@
      */
     void sysfsNodeChanged(String sysfsNodePath);
 
+    /**
+     * Notify if Accessibility bounce keys threshold is changed from InputSettings.
+     */
+    void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -500,5 +505,8 @@
 
         @Override
         public native void sysfsNodeChanged(String sysfsNodePath);
+
+        @Override
+        public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index dcb86a7..66807ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -23,7 +23,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -64,6 +66,7 @@
     private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
     private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
 
+    private final Context mContext;
     // This must be the looper for the UiThread.
     private final Looper mLooper;
     private final InputManagerInternal mInputManagerInternal;
@@ -87,7 +90,9 @@
     private int mCurrentRequestId;
 
     @AnyThread
-    HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) {
+    HandwritingModeController(Context context, Looper uiThreadLooper,
+            Runnable inkWindowInitRunnable) {
+        mContext = context;
         mLooper = uiThreadLooper;
         mCurrentDisplayId = Display.INVALID_DISPLAY;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -285,7 +290,14 @@
         mHandwritingSurface.startIntercepting(imePid, imeUid);
 
         // Unset the pointer icon for the stylus in case the app had set it.
-        InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+                    PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+                    downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+                    mHandwritingSurface.getInputChannel().getToken());
+        } else {
+            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        }
 
         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
                 mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 09107c1..30e9f5b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1713,7 +1713,7 @@
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         mNonPreemptibleInputMethods = mRes.getStringArray(
                 com.android.internal.R.array.config_nonPreemptibleInputMethods);
-        mHwController = new HandwritingModeController(thread.getLooper(),
+        mHwController = new HandwritingModeController(mContext, thread.getLooper(),
                 new InkWindowInitializer());
         registerDeviceListenerAndCheckStylusSupport();
     }
@@ -5275,7 +5275,7 @@
                     return true;
                 }
             }
-            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+            mSettings.appendAndPutEnabledInputMethodLocked(id);
             // Previous state was disabled.
             return false;
         } else {
@@ -5612,7 +5612,7 @@
                 if (enabled) {
                     if (!settings.getEnabledInputMethodListLocked().contains(
                             methodMap.get(imeId))) {
-                        settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+                        settings.appendAndPutEnabledInputMethodLocked(imeId);
                     }
                 } else {
                     settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
@@ -6357,7 +6357,7 @@
                         }
                     }
                     if (!previouslyEnabled) {
-                        settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+                        settings.appendAndPutEnabledInputMethodLocked(imeId);
                     }
                 }
             } else {
@@ -6501,7 +6501,7 @@
                         settings.putEnabledInputMethodsStr("");
                         nextEnabledImes.forEach(
                                 imi -> settings.appendAndPutEnabledInputMethodLocked(
-                                        imi.getId(), false));
+                                        imi.getId()));
 
                         // Reset selected IME.
                         settings.putSelectedInputMethod(nextIme);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index acf9a7f..2128356 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -362,10 +362,7 @@
             return result;
         }
 
-        void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
-            if (reloadInputMethodStr) {
-                getEnabledInputMethodsStr();
-            }
+        void appendAndPutEnabledInputMethodLocked(String id) {
             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
                 // Add in the newly enabled input method.
                 putEnabledInputMethodsStr(id);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 10b6052..e5807e8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -82,6 +82,7 @@
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -272,9 +273,10 @@
         CertPath certPath;
         X509Certificate rootCert =
                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
+        Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
         try {
             Log.d(TAG, "Getting and validating a random endpoint certificate");
-            certPath = certXml.getRandomEndpointCert(rootCert);
+            certPath = certXml.getRandomEndpointCert(rootCert, validationDate);
         } catch (CertValidationException e) {
             Log.e(TAG, "Invalid endpoint cert", e);
             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
@@ -348,10 +350,11 @@
 
         X509Certificate rootCert =
                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
+        Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
         try {
-            sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
+            sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile, validationDate);
         } catch (CertValidationException e) {
-            Log.d(TAG, "The signature over the cert file is invalid."
+            Log.e(TAG, "The signature over the cert file is invalid."
                     + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
                     + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
@@ -601,8 +604,9 @@
         }
 
         try {
-            CertUtils.validateCertPath(
-                    mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath);
+            Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
+            CertUtils.validateCertPath(mTestCertHelper.getRootCertificate(rootCertificateAlias),
+                    certPath, validationDate);
         } catch (CertValidationException e) {
             Log.e(TAG, "Failed to validate the given cert path", e);
             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
index c963f79..4a1cae2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
@@ -29,6 +29,7 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.security.cert.X509Certificate;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -67,6 +68,18 @@
         return rootCertificate;
     }
 
+    /**
+     * Returns hardcoded validation date for e2e tests.
+     */
+    public @Nullable Date getValidationDate(String rootCertificateAlias) {
+        if (isTestOnlyCertificateAlias(rootCertificateAlias)) {
+            // Certificate used for e2e test is expired.
+            return new Date(2019 - 1900, 1, 30);
+        } else {
+            return null; // Use current time
+        }
+    }
+
     public @NonNull String getDefaultCertificateAliasIfEmpty(
             @Nullable String rootCertificateAlias) {
         if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
index 26e8270..0881275 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -305,12 +305,13 @@
      *
      * @param trustedRoot the trusted root certificate
      * @param certPath the certificate path to be validated
+     * @param validationDate use null for current time
      * @throws CertValidationException if the given certificate path is invalid, e.g., is expired,
      *                                 or does not have a valid signature
      */
-    public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath)
-            throws CertValidationException {
-        validateCertPath(/*validationDate=*/ null, trustedRoot, certPath);
+    public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath,
+                @Nullable Date validationDate) throws CertValidationException {
+        validateCertPath(validationDate, trustedRoot, certPath);
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
index ff22a8d..d159a84 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
@@ -76,15 +76,16 @@
      * and returns the certificate path including the chosen certificate if it is valid.
      *
      * @param trustedRoot the trusted root certificate
+     * @param validationDate use null for current time
      * @return the certificate path including the chosen certificate if the certificate is valid
      * @throws CertValidationException if the chosen certificate cannot be validated based on the
      *                                 trusted root certificate
      */
-    public CertPath getRandomEndpointCert(X509Certificate trustedRoot)
-            throws CertValidationException {
+    public CertPath getRandomEndpointCert(X509Certificate trustedRoot,
+            @Nullable Date validationDate)throws CertValidationException {
         return getEndpointCert(
                 new SecureRandom().nextInt(this.endpointCerts.size()),
-                /*validationDate=*/ null,
+                validationDate,
                 trustedRoot);
     }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
index e75be85..c3f4f55 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
@@ -18,7 +18,7 @@
 
 import android.annotation.Nullable;
 
-import com.android.internal.annotations.VisibleForTesting;
+import org.w3c.dom.Element;
 
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -26,8 +26,6 @@
 import java.util.Date;
 import java.util.List;
 
-import org.w3c.dom.Element;
-
 /**
  * Parses and holds the XML file containing the signature of the XML file containing the list of THM
  * public-key certificates.
@@ -58,17 +56,13 @@
      *
      * @param trustedRoot     the trusted root certificate
      * @param signedFileBytes the original file content that has been signed
+     * @param validationDate use null for current time
+     *
      * @throws CertValidationException if the signature verification fails, or the signer's
      *                                 certificate contained in this XML file cannot be validated
      *                                 based on the trusted root certificate
      */
-    public void verifyFileSignature(X509Certificate trustedRoot, byte[] signedFileBytes)
-            throws CertValidationException {
-        verifyFileSignature(trustedRoot, signedFileBytes, /*validationDate=*/ null);
-    }
-
-    @VisibleForTesting
-    void verifyFileSignature(
+    public void verifyFileSignature(
             X509Certificate trustedRoot, byte[] signedFileBytes, @Nullable Date validationDate)
             throws CertValidationException {
         CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, signerCert);
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 173c452..8504495 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -330,8 +330,11 @@
                 TextUtils.isEmpty(address)
                         ? null
                         : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
-        return createMediaRoute2Info(
-                routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address);
+        // We use the name from the port instead AudioDeviceInfo#getProductName because the latter
+        // replaces empty names with the name of the device (example: Pixel 8). In that case we want
+        // to derive a name ourselves from the type instead.
+        String deviceName = audioDeviceInfo.getPort().name();
+        return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address);
     }
 
     /**
@@ -339,8 +342,8 @@
      *
      * @param routeId A route id, or null to use an id pre-defined for the given {@code type}.
      * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}.
-     * @param productName The product name as obtained from {@link
-     *     AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code
+     * @param deviceName A human readable name to populate the route's {@link
+     *     MediaRoute2Info#getName name}, or null to use a predefined name for the given {@code
      *     type}.
      * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link
      *     BluetoothDevice#getAddress()}.
@@ -350,7 +353,7 @@
     private MediaRoute2Info createMediaRoute2Info(
             @Nullable String routeId,
             int audioDeviceInfoType,
-            @Nullable CharSequence productName,
+            @Nullable CharSequence deviceName,
             @Nullable String address) {
         SystemRouteInfo systemRouteInfo =
                 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType);
@@ -359,7 +362,7 @@
             // earpiece.
             return null;
         }
-        CharSequence humanReadableName = productName;
+        CharSequence humanReadableName = deviceName;
         if (TextUtils.isEmpty(humanReadableName)) {
             humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource);
         }
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 9fdeda4..8855666 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,14 +16,23 @@
 
 package com.android.server.notification;
 
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.ColorDisplayManager;
 import android.os.Binder;
 import android.os.PowerManager;
 import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
+import com.android.internal.annotations.GuardedBy;
 
 /** Default implementation for {@link DeviceEffectsApplier}. */
 class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
@@ -34,13 +43,24 @@
     private static final int SATURATION_LEVEL_FULL_COLOR = 100;
     private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f;
     private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f;
+    private static final IntentFilter SCREEN_OFF_INTENT_FILTER = new IntentFilter(
+            Intent.ACTION_SCREEN_OFF);
 
+    private final Context mContext;
     private final ColorDisplayManager mColorDisplayManager;
     private final PowerManager mPowerManager;
     private final UiModeManager mUiModeManager;
     private final WallpaperManager mWallpaperManager;
 
+    private final Object mRegisterReceiverLock = new Object();
+    @GuardedBy("mRegisterReceiverLock")
+    private boolean mIsScreenOffReceiverRegistered;
+
+    private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build();
+    private boolean mPendingNightMode;
+
     DefaultDeviceEffectsApplier(Context context) {
+        mContext = context;
         mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
         mPowerManager = context.getSystemService(PowerManager.class);
         mUiModeManager = context.getSystemService(UiModeManager.class);
@@ -48,24 +68,90 @@
     }
 
     @Override
-    public void apply(ZenDeviceEffects effects) {
+    public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) {
         Binder.withCleanCallingIdentity(() -> {
-            mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
-                    effects.shouldSuppressAmbientDisplay());
-
-            if (mColorDisplayManager != null) {
-                mColorDisplayManager.setSaturationLevel(
-                        effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
-                                : SATURATION_LEVEL_FULL_COLOR);
+            if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
+                    != effects.shouldSuppressAmbientDisplay()) {
+                mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+                        effects.shouldSuppressAmbientDisplay());
             }
 
-            if (mWallpaperManager != null) {
-                mWallpaperManager.setWallpaperDimAmount(
-                        effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
-                                : WALLPAPER_DIM_AMOUNT_NORMAL);
+            if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
+                if (mColorDisplayManager != null) {
+                    mColorDisplayManager.setSaturationLevel(
+                            effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+                                    : SATURATION_LEVEL_FULL_COLOR);
+                }
             }
 
-            // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off.
+            if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
+                if (mWallpaperManager != null) {
+                    mWallpaperManager.setWallpaperDimAmount(
+                            effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+                                    : WALLPAPER_DIM_AMOUNT_NORMAL);
+                }
+            }
+
+            if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) {
+                updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+            }
         });
+
+        mLastAppliedEffects = effects;
+    }
+
+    private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) {
+        mPendingNightMode = useNightMode;
+
+        // Changing the theme can be disruptive for the user (Activities are likely recreated, may
+        // lose some state). Therefore we only apply the change immediately if the rule was
+        // activated manually, or we are initializing, or the screen is currently off/dreaming.
+        if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
+                || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
+                || origin == ZenModeConfig.UPDATE_ORIGIN_USER
+                || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                || !mPowerManager.isInteractive()) {
+            unregisterScreenOffReceiver();
+            updateNightModeImmediately(useNightMode);
+        } else {
+            registerScreenOffReceiver();
+        }
+    }
+
+    @GuardedBy("mRegisterReceiverLock")
+    private final BroadcastReceiver mNightModeWhenScreenOff = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            unregisterScreenOffReceiver();
+            updateNightModeImmediately(mPendingNightMode);
+        }
+    };
+
+    private void updateNightModeImmediately(boolean useNightMode) {
+        Binder.withCleanCallingIdentity(() -> {
+            // TODO: b/314285749 - Placeholder; use real APIs when available.
+            mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+            mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                    useNightMode);
+        });
+    }
+
+    private void registerScreenOffReceiver() {
+        synchronized (mRegisterReceiverLock) {
+            if (!mIsScreenOffReceiverRegistered) {
+                mContext.registerReceiver(mNightModeWhenScreenOff, SCREEN_OFF_INTENT_FILTER,
+                        Context.RECEIVER_NOT_EXPORTED);
+                mIsScreenOffReceiverRegistered = true;
+            }
+        }
+    }
+
+    private void unregisterScreenOffReceiver() {
+        synchronized (mRegisterReceiverLock) {
+            if (mIsScreenOffReceiverRegistered) {
+                mIsScreenOffReceiverRegistered = false;
+                mContext.unregisterReceiver(mNightModeWhenScreenOff);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 02845fb..a62d8b8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@
 import static android.service.notification.NotificationListenerService.Ranking.RANKING_UNCHANGED;
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
+import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
@@ -414,6 +415,12 @@
 
     static final long SNOOZE_UNTIL_UNSPECIFIED = -1;
 
+    /**
+     *  The threshold, in milliseconds, to determine whether a notification has been
+     * cleared too quickly.
+     */
+    private static final int NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS = 5000;
+
     static final int INVALID_UID = -1;
     static final String ROOT_PKG = "root";
 
@@ -4817,9 +4824,12 @@
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final long identity = Binder.clearCallingIdentity();
+            boolean notificationsRapidlyCleared = false;
+            final String pkg;
             try {
                 synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    pkg = info.component.getPackageName();
 
                     // Cancellation reason. If the token comes from assistant, label the
                     // cancellation as coming from the assistant; default to LISTENER_CANCEL.
@@ -4838,11 +4848,19 @@
                                     !mUserProfiles.isCurrentProfile(userId)) {
                                 continue;
                             }
+                            notificationsRapidlyCleared = notificationsRapidlyCleared
+                                    || isNotificationRecent(r);
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
                                     r.getSbn().getId(), userId, reason);
                         }
                     } else {
+                        for (NotificationRecord notificationRecord : mNotificationList) {
+                            if (isNotificationRecent(notificationRecord)) {
+                                notificationsRapidlyCleared = true;
+                                break;
+                            }
+                        }
                         if (lifetimeExtensionRefactor()) {
                             cancelAllLocked(callingUid, callingPid, info.userid,
                                     REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(),
@@ -4855,11 +4873,23 @@
                         }
                     }
                 }
+                if (notificationsRapidlyCleared) {
+                    mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+                            callingUid, pkg, /* attributionTag= */ null, /* message= */ null);
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
 
+        private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) {
+            if (!rapidClearNotificationsByListenerAppOpEnabled()) {
+                return false;
+            }
+            return notificationRecord.getFreshnessMs(System.currentTimeMillis())
+                    < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
+        }
+
         /**
          * Handle request from an approved listener to re-enable itself.
          *
@@ -5299,11 +5329,11 @@
         public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
             enforceSystemOrSystemUI("INotificationManager.setZenMode");
             final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
             final long identity = Binder.clearCallingIdentity();
             try {
-                mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid,
-                        isSystemOrSystemUi);
+                mZenModeHelper.setManualZenMode(mode, conditionId,
+                        ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+                        reason, /* caller= */ null, callingUid);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5334,14 +5364,7 @@
 
         @Override
         public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) {
-            Objects.requireNonNull(automaticZenRule, "automaticZenRule is null");
-            Objects.requireNonNull(automaticZenRule.getName(), "Name is null");
-            if (automaticZenRule.getOwner() == null
-                    && automaticZenRule.getConfigurationActivity() == null) {
-                throw new NullPointerException(
-                        "Rule must have a conditionproviderservice and/or configuration activity");
-            }
-            Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null");
+            validateAutomaticZenRule(automaticZenRule);
             checkCallerIsSameApp(pkg);
             if (automaticZenRule.getZenPolicy() != null
                     && automaticZenRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) {
@@ -5360,30 +5383,47 @@
             }
 
             return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
-                    "addAutomaticZenRule", Binder.getCallingUid(),
-                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
-                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
-                            : ZenModeHelper.FROM_APP);
+                    // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
+                    //  manually in Settings).
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "addAutomaticZenRule", Binder.getCallingUid());
         }
 
         @Override
-        public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule)
-                throws RemoteException {
-            Objects.requireNonNull(automaticZenRule, "automaticZenRule is null");
-            Objects.requireNonNull(automaticZenRule.getName(), "Name is null");
-            if (automaticZenRule.getOwner() == null
-                    && automaticZenRule.getConfigurationActivity() == null) {
+        public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+            validateAutomaticZenRule(automaticZenRule);
+            enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
+            //  manually in Settings).
+            return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "updateAutomaticZenRule", Binder.getCallingUid());
+        }
+
+        private void validateAutomaticZenRule(AutomaticZenRule rule) {
+            Objects.requireNonNull(rule, "automaticZenRule is null");
+            Objects.requireNonNull(rule.getName(), "Name is null");
+            if (rule.getOwner() == null
+                    && rule.getConfigurationActivity() == null) {
                 throw new NullPointerException(
                         "Rule must have a conditionproviderservice and/or configuration activity");
             }
-            Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null");
-            enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+            Objects.requireNonNull(rule.getConditionId(), "ConditionId is null");
 
-            return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
-                    "updateAutomaticZenRule", Binder.getCallingUid(),
-                    // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
-                    isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
-                            : ZenModeHelper.FROM_APP);
+            if (android.app.Flags.modesApi()) {
+                if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) {
+                    int uid = Binder.getCallingUid();
+                    boolean isDeviceOwner = Binder.withCleanCallingIdentity(
+                            () -> mDpm.isActiveDeviceOwner(uid));
+                    if (!isDeviceOwner) {
+                        throw new IllegalArgumentException(
+                                "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED");
+                    }
+                }
+            }
         }
 
         @Override
@@ -5392,8 +5432,12 @@
             // Verify that they can modify zen rules.
             enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
 
-            return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule",
-                    Binder.getCallingUid(), isCallerSystemOrSystemUi());
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
+            //  manually in Settings).
+            return mZenModeHelper.removeAutomaticZenRule(id,
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    "removeAutomaticZenRule", Binder.getCallingUid());
         }
 
         @Override
@@ -5402,8 +5446,9 @@
             enforceSystemOrSystemUI("removeAutomaticZenRules");
 
             return mZenModeHelper.removeAutomaticZenRules(packageName,
-                    packageName + "|removeAutomaticZenRules", Binder.getCallingUid(),
-                    isCallerSystemOrSystemUi());
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
         }
 
         @Override
@@ -5421,8 +5466,12 @@
 
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
 
-            mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(),
-                    isCallerSystemOrSystemUi());
+            // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
+            //  manually in Settings).
+            mZenModeHelper.setAutomaticZenRuleState(id, condition,
+                    isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                            : ZenModeConfig.UPDATE_ORIGIN_APP,
+                    Binder.getCallingUid());
         }
 
         @Override
@@ -5440,8 +5489,11 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter",
-                        callingUid, isSystemOrSystemUi);
+                mZenModeHelper.setManualZenMode(zen, null,
+                        isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                                : ZenModeConfig.UPDATE_ORIGIN_APP,
+                        /* reason= */ "setInterruptionFilter", /* caller= */ pkg,
+                        callingUid);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5806,7 +5858,10 @@
                 } else {
                     ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
                             policy);
-                    mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi);
+                    mZenModeHelper.setNotificationPolicy(policy,
+                            isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                                    : ZenModeConfig.UPDATE_ORIGIN_APP,
+                            callingUid);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to set notification policy", e);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 6a7eebb..1bafcfe 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -59,6 +59,7 @@
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.RankingHelperProto;
+import android.service.notification.ZenModeConfig;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -160,7 +161,6 @@
     static final boolean DEFAULT_BUBBLES_ENABLED = true;
     @VisibleForTesting
     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
-    static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true;
 
     private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP = 0;
     private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER = 1;
@@ -199,7 +199,7 @@
     private SparseBooleanArray mBubblesEnabled;
     private SparseBooleanArray mLockScreenShowNotifications;
     private SparseBooleanArray mLockScreenPrivateNotifications;
-    private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
+    private boolean mIsMediaNotificationFilteringEnabled;
     // When modes_api flag is enabled, this value only tracks whether the current user has any
     // channels marked as "priority channels", but not necessarily whether they are permitted
     // to bypass DND by current zen policy.
@@ -223,6 +223,8 @@
         mAppOps = appOpsManager;
         mUserProfiles = userProfiles;
         mShowReviewPermissionsNotification = showReviewPermissionsNotification;
+        mIsMediaNotificationFilteringEnabled = context.getResources()
+                .getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
 
         XML_VERSION = 4;
 
@@ -1862,12 +1864,16 @@
     public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
             boolean fromSystemOrSystemUi) {
         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
-        mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
-                policy.priorityCategories, policy.priorityCallSenders,
-                policy.priorityMessageSenders, policy.suppressedVisualEffects,
-                (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
-                        : 0),
-                policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi);
+        mZenModeHelper.setNotificationPolicy(
+                new NotificationManager.Policy(
+                        policy.priorityCategories, policy.priorityCallSenders,
+                        policy.priorityMessageSenders, policy.suppressedVisualEffects,
+                        (areChannelsBypassingDnd
+                                ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
+                        policy.priorityConversationSenders),
+                fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid);
     }
 
     // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
@@ -2687,8 +2693,11 @@
 
     /** Requests check of the feature setting for showing media notifications in quick settings. */
     public void updateMediaNotificationFilteringEnabled() {
+        // TODO(b/192412820): Consolidate SHOW_MEDIA_ON_QUICK_SETTINGS into compile-time value.
         final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0;
+                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0
+                        && mContext.getResources().getBoolean(
+                                R.bool.config_quickSettingsShowMediaPlayer);
         if (newValue != mIsMediaNotificationFilteringEnabled) {
             mIsMediaNotificationFilteringEnabled = newValue;
             updateConfig();
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 6ecd799..86aa2d8 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -111,8 +111,10 @@
     public void onServiceAdded(ComponentName component) {
         if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
         final int callingUid = Binder.getCallingUid();
-        mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded:" + component,
-                callingUid, callingUid == Process.SYSTEM_UID);
+        mHelper.setConfig(mHelper.getConfig(), component,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                "zmc.onServiceAdded:" + component, callingUid);
     }
 
     @Override
@@ -121,8 +123,10 @@
         ZenModeConfig config = mHelper.getConfig();
         if (config == null) return;
         final int callingUid = Binder.getCallingUid();
-        mHelper.setAutomaticZenRuleState(id, condition, callingUid,
-                callingUid == Process.SYSTEM_UID);
+        mHelper.setAutomaticZenRuleState(id, condition,
+                callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        : ZenModeConfig.UPDATE_ORIGIN_APP,
+                callingUid);
     }
 
     // Only valid for CPS backed rules
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 218519f..7ec94c3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,11 +24,16 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
 import android.annotation.DrawableRes;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -79,6 +84,7 @@
 import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.notification.ZenModeProto;
 import android.service.notification.ZenPolicy;
@@ -111,8 +117,6 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -137,21 +141,6 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;
 
-    /** A rule addition or update that is initiated by the System or SystemUI. */
-    static final int FROM_SYSTEM_OR_SYSTEMUI = 1;
-    /** A rule addition or update that is initiated by the user (through system settings). */
-    static final int FROM_USER = 2;
-    /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */
-    static final int FROM_APP = 3;
-
-    @IntDef(prefix = { "FROM_" }, value = {
-            FROM_SYSTEM_OR_SYSTEMUI,
-            FROM_USER,
-            FROM_APP
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface ChangeOrigin {}
-
     // pkg|userId => uid
     @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
@@ -160,7 +149,7 @@
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     private final NotificationManager mNotificationManager;
-    private ZenModeConfig mDefaultConfig;
+    private final ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
     private final RingerModeDelegate mRingerModeDelegate = new
@@ -275,9 +264,8 @@
             // "update" config to itself, which will have no effect in the case where a config
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
-            updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/,
-                    Process.SYSTEM_UID /* callingUid */, true /* is system */,
-                    false /* no broadcasts*/);
+            updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init",
+                    true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */);
         }
     }
 
@@ -297,24 +285,20 @@
     /**
      * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
      *
-     * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig}
-     * that includes activated rules), they will be applied immediately.
+     * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be
+     * applied immediately.
      */
     void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
         if (!Flags.modesApi()) {
             return;
         }
-        ZenDeviceEffects consolidatedDeviceEffects;
         synchronized (mConfigLock) {
             if (mDeviceEffectsApplier != null) {
                 throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
             }
             mDeviceEffectsApplier = deviceEffectsApplier;
-            consolidatedDeviceEffects = mConsolidatedDeviceEffects;
         }
-        if (consolidatedDeviceEffects.hasEffects()) {
-            applyConsolidatedDeviceEffects();
-        }
+        applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT);
     }
 
     public void onUserSwitched(int user) {
@@ -353,7 +337,8 @@
             config.user = user;
         }
         synchronized (mConfigLock) {
-            setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+            setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason,
+                    Process.SYSTEM_UID);
         }
         cleanUpZenRules();
     }
@@ -366,9 +351,11 @@
             boolean fromSystemOrSystemUi) {
         final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
         if (newZen != -1) {
-            setManualZenMode(newZen, null, name != null ? name.getPackageName() : null,
-                    "listener:" + (name != null ? name.flattenToShortString() : null),
-                    callingUid, fromSystemOrSystemUi);
+            setManualZenMode(newZen, null,
+                    fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP,
+                    /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
+                    /* caller= */ name != null ? name.getPackageName() : null,
+                    callingUid);
         }
     }
 
@@ -428,7 +415,7 @@
     }
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, @ChangeOrigin int origin) {
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -462,10 +449,9 @@
             }
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
-            populateZenRule(pkg, automaticZenRule, rule, true, origin);
+            populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
             newConfig.automaticRules.put(rule.id, rule);
-            if (setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    origin == FROM_SYSTEM_OR_SYSTEMUI)) {
+            if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
                 return rule.id;
             } else {
                 throw new AndroidRuntimeException("Could not create rule");
@@ -474,7 +460,7 @@
     }
 
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
-            String reason, int callingUid, @ChangeOrigin int origin) {
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -502,9 +488,9 @@
                 }
             }
 
-            populateZenRule(rule.pkg, automaticZenRule, rule, false, origin);
-            return setConfigLocked(newConfig, reason, rule.component, true, callingUid,
-                    origin == FROM_SYSTEM_OR_SYSTEMUI);
+            populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+            return setConfigLocked(newConfig, origin, reason,
+                    rule.component, true, callingUid);
         }
     }
 
@@ -541,8 +527,7 @@
                     Condition deactivated = new Condition(rule.conditionId,
                             mContext.getString(R.string.zen_mode_implicit_deactivated),
                             Condition.STATE_FALSE);
-                    setAutomaticZenRuleState(rule.id, deactivated,
-                            callingUid, /* fromSystemOrSystemUi= */ false);
+                    setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid);
                 }
             } else {
                 // Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -558,9 +543,8 @@
                 rule.condition = new Condition(rule.conditionId,
                         mContext.getString(R.string.zen_mode_implicit_activated),
                         Condition.STATE_TRUE);
-                setConfigLocked(newConfig, /* triggeringComponent= */ null,
-                        "applyGlobalZenModeAsImplicitZenRule",
-                        callingUid, /* fromSystemOrSystemUi= */ false);
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                        "applyGlobalZenModeAsImplicitZenRule", callingUid);
             }
         }
     }
@@ -595,9 +579,8 @@
             }
             // TODO: b/308673679 - Keep user customization of this rule!
             rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
-            setConfigLocked(newConfig, /* triggeringComponent= */ null,
-                    "applyGlobalPolicyAsImplicitZenRule",
-                    callingUid, /* fromSystemOrSystemUi= */ false);
+            setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                    "applyGlobalPolicyAsImplicitZenRule", callingUid);
         }
     }
 
@@ -669,8 +652,8 @@
         return "implicit_" + forPackage;
     }
 
-    public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
+            int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -695,13 +678,12 @@
             }
             dispatchOnAutomaticRuleStatusChanged(
                     mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
-            return setConfigLocked(newConfig, reason, null, true, callingUid,
-                    fromSystemOrSystemUi);
+            return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
-    public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
+            String reason, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -712,13 +694,12 @@
                     newConfig.automaticRules.removeAt(i);
                 }
             }
-            return setConfigLocked(newConfig, reason, null, true, callingUid,
-                    fromSystemOrSystemUi);
+            return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
-    public void setAutomaticZenRuleState(String id, Condition condition, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
+            int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -726,13 +707,12 @@
             newConfig = mConfig.copy();
             ArrayList<ZenRule> rules = new ArrayList<>();
             rules.add(newConfig.automaticRules.get(id));
-            setAutomaticZenRuleStateLocked(newConfig, rules, condition, callingUid,
-                    fromSystemOrSystemUi);
+            setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
         }
     }
 
-    public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
+            @ConfigChangeOrigin int origin, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -740,20 +720,23 @@
 
             setAutomaticZenRuleStateLocked(newConfig,
                     findMatchingRules(newConfig, ruleDefinition, condition),
-                    condition, callingUid, fromSystemOrSystemUi);
+                    condition, origin, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
-            Condition condition, int callingUid, boolean fromSystemOrSystemUi) {
+            Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
         if (rules == null || rules.isEmpty()) return;
 
+        if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) {
+            origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
+        }
+
         for (ZenRule rule : rules) {
             rule.condition = condition;
             updateSnoozing(rule);
-            setConfigLocked(config, rule.component, "conditionChanged", callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
         }
     }
 
@@ -857,7 +840,7 @@
                         // update default rule (if locale changed, name of rule will change)
                         currRule.name = defaultRule.name;
                         updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
-                                "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI);
+                                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid);
                     }
                 }
             }
@@ -899,13 +882,12 @@
         return null;
     }
 
-    @VisibleForTesting
     void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
-            boolean isNew, @ChangeOrigin int origin) {
-            // TODO: b/308671593,b/311406021 - Handle origins more precisely:
-            //  - FROM_USER can override anything and updates bitmask of user-modified fields;
-            //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
-            //  - FROM_APP can only update if not user-modified.
+            @ConfigChangeOrigin int origin, boolean isNew) {
+        // TODO: b/308671593,b/311406021 - Handle origins more precisely:
+        //  - USER can override anything and updates bitmask of user-modified fields;
+        //  - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - APP can only update if not user-modified.
         if (rule.enabled != automaticZenRule.isEnabled()) {
             rule.snoozing = false;
         }
@@ -951,12 +933,12 @@
      */
     @Nullable
     private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
-            @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) {
+            @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) {
         // TODO: b/308671593,b/311406021 - Handle origins more precisely:
-        //  - FROM_USER can override anything and updates bitmask of user-modified fields;
-        //  - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
-        //  - FROM_APP can only update if not user-modified.
-        if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) {
+        //  - USER can override anything and updates bitmask of user-modified fields;
+        //  - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+        //  - APP can only update if not user-modified.
+        if (origin != UPDATE_ORIGIN_APP) {
             return newEffects;
         }
 
@@ -1033,16 +1015,16 @@
                 : AUTOMATIC_RULE_STATUS_DISABLED);
     }
 
-    public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason,
-            int callingUid, boolean fromSystemOrSystemUi) {
-        setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid,
-                fromSystemOrSystemUi);
+    void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+            String reason, @Nullable String caller, int callingUid) {
+        setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
+                callingUid);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
     }
 
-    private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,
-            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
+    private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+            String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -1069,8 +1051,7 @@
                 }
                 newConfig.manualRule = newRule;
             }
-            setConfigLocked(newConfig, reason, null, setRingerMode, callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid);
         }
     }
 
@@ -1199,7 +1180,9 @@
             }
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
-                setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+                setConfigLocked(config, null,
+                        forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason,
+                        Process.SYSTEM_UID);
             }
         }
     }
@@ -1233,13 +1216,13 @@
     /**
      * Sets the global notification policy used for priority only do not disturb
      */
-    public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) {
+    public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin,
+            int callingUid) {
         synchronized (mConfigLock) {
             if (policy == null || mConfig == null) return;
             final ZenModeConfig newConfig = mConfig.copy();
             newConfig.applyNotificationPolicy(policy);
-            setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid,
-                    fromSystemOrSystemUi);
+            setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
         }
     }
 
@@ -1264,8 +1247,8 @@
                     }
                 }
             }
-            setConfigLocked(newConfig, null, "cleanUpZenRules", Process.SYSTEM_UID,
-                    true);
+            setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
+                    Process.SYSTEM_UID);
         }
     }
 
@@ -1287,22 +1270,22 @@
 
     @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
-            String reason, int callingUid, boolean fromSystemOrSystemUi) {
-        return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/,
-                callingUid, fromSystemOrSystemUi);
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
+                callingUid);
     }
 
-    public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason,
-            int callingUid, boolean fromSystemOrSystemUi) {
+    void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
+            @ConfigChangeOrigin int origin, String reason, int callingUid) {
         synchronized (mConfigLock) {
-            setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi);
+            setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
-    private boolean setConfigLocked(ZenModeConfig config, String reason,
-            ComponentName triggeringComponent, boolean setRingerMode, int callingUid,
-            boolean fromSystemOrSystemUi) {
+    private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+            String reason, ComponentName triggeringComponent, boolean setRingerMode,
+            int callingUid) {
         final long identity = Binder.clearCallingIdentity();
         try {
             if (config == null || !config.isValid()) {
@@ -1332,8 +1315,7 @@
             if (policyChanged) {
                 dispatchOnPolicyChanged();
             }
-            updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid,
-                    fromSystemOrSystemUi, true);
+            updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid);
             mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
             return true;
         } catch (SecurityException e) {
@@ -1349,17 +1331,16 @@
      * If logging is enabled, will also request logging of the outcome of this change if needed.
      */
     @GuardedBy("mConfigLock")
-    private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
-            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi,
-            boolean sendBroadcasts) {
+    private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+            String reason, boolean setRingerMode, int callingUid) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
         // Store (a copy of) all config and zen mode info prior to any changes taking effect
         ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo(
                 mZenMode, mConfig, mConsolidatedPolicy);
         if (!config.equals(mConfig)) {
-            // schedule broadcasts
-            if (Flags.modesApi() && sendBroadcasts) {
+            // Schedule broadcasts. Cannot be sent during boot, though.
+            if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) {
                 for (ZenRule rule : config.automaticRules.values()) {
                     ZenRule original = mConfig.automaticRules.get(rule.id);
                     if (original != null) {
@@ -1377,15 +1358,19 @@
 
             mConfig = config;
             dispatchOnConfigChanged();
-            updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+            updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
         }
         final String val = Integer.toString(config.hashCode());
         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        evaluateZenModeLocked(reason, setRingerMode);
+        evaluateZenModeLocked(origin, reason, setRingerMode);
         // After all changes have occurred, log if requested
         if (logZenModeEvents) {
             ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
                     mZenMode, mConfig, mConsolidatedPolicy);
+            boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                    || origin == UPDATE_ORIGIN_INIT
+                    || origin == UPDATE_ORIGIN_INIT_USER
+                    || origin == UPDATE_ORIGIN_RESTORE_BACKUP;
             mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid,
                     fromSystemOrSystemUi);
         }
@@ -1416,7 +1401,8 @@
 
     @VisibleForTesting
     @GuardedBy("mConfigLock")
-    protected void evaluateZenModeLocked(String reason, boolean setRingerMode) {
+    protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason,
+            boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
         if (mConfig == null) return;
         final int policyHashBefore = mConsolidatedPolicy == null ? 0
@@ -1426,7 +1412,7 @@
         ZenLog.traceSetZenMode(zen, reason);
         mZenMode = zen;
         setZenModeSetting(mZenMode);
-        updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+        updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
         boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
                 zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
                         && policyHashBefore != mConsolidatedPolicy.hashCode()));
@@ -1468,6 +1454,7 @@
         }
     }
 
+    @GuardedBy("mConfigLock")
     private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
         if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             policy.apply(new ZenPolicy.Builder()
@@ -1487,7 +1474,9 @@
         }
     }
 
-    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) {
+    @GuardedBy("mConfigLock")
+    private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin,
+            String reason) {
         synchronized (mConfigLock) {
             if (mConfig == null) return;
             ZenPolicy policy = new ZenPolicy();
@@ -1519,13 +1508,13 @@
                 ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
                 if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
                     mConsolidatedDeviceEffects = deviceEffects;
-                    mHandler.postApplyDeviceEffects();
+                    mHandler.postApplyDeviceEffects(origin);
                 }
             }
         }
     }
 
-    private void applyConsolidatedDeviceEffects() {
+    private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) {
         if (!Flags.modesApi()) {
             return;
         }
@@ -1536,7 +1525,7 @@
             effects = mConsolidatedDeviceEffects;
         }
         if (applier != null) {
-            applier.apply(effects);
+            applier.apply(effects, source);
         }
     }
 
@@ -1801,8 +1790,8 @@
         }
 
         @Override
-        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
-                int ringerModeExternal, VolumePolicy policy) {
+        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew,
+                @Nullable String caller, int ringerModeExternal, VolumePolicy policy) {
             final boolean isChange = ringerModeOld != ringerModeNew;
 
             int ringerModeExternalOut = ringerModeNew;
@@ -1839,8 +1828,9 @@
             }
 
             if (newZen != -1) {
-                setManualZenMode(newZen, null, "ringerModeInternal", null,
-                        false /*setRingerMode*/, Process.SYSTEM_UID, true);
+                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
+                        Process.SYSTEM_UID);
             }
             if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
                 ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
@@ -1856,8 +1846,8 @@
         }
 
         @Override
-        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
-                int ringerModeInternal, VolumePolicy policy) {
+        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew,
+                @Nullable String caller, int ringerModeInternal, VolumePolicy policy) {
             int ringerModeInternalOut = ringerModeNew;
             final boolean isChange = ringerModeOld != ringerModeNew;
             final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
@@ -1883,8 +1873,8 @@
                     break;
             }
             if (newZen != -1) {
-                setManualZenMode(newZen, null, "ringerModeExternal", caller,
-                        false /*setRingerMode*/, Process.SYSTEM_UID, true);
+                setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
             }
 
             ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
@@ -2024,11 +2014,9 @@
         }
         try {
             final Resources res = mPm.getResourcesForApplication(packageName);
-            final String fullName = res.getResourceName(resId);
-
-            return fullName;
+            return res.getResourceName(resId);
         } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
-            Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+            Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
                     + ". Resource IDs may change when the application is upgraded, and the system"
                     + " may not be able to find the correct resource.");
             return null;
@@ -2149,9 +2137,9 @@
             sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
         }
 
-        private void postApplyDeviceEffects() {
+        private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
             removeMessages(MSG_APPLY_EFFECTS);
-            sendEmptyMessage(MSG_APPLY_EFFECTS);
+            sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0));
         }
 
         @Override
@@ -2168,7 +2156,8 @@
                     updateRingerAndAudio(shouldApplyToRinger);
                     break;
                 case MSG_APPLY_EFFECTS:
-                    applyConsolidatedDeviceEffects();
+                    @ConfigChangeOrigin int origin = msg.arg1;
+                    applyConsolidatedDeviceEffects(origin);
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dcac8c9..da01745 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -20,3 +20,17 @@
   description: "This flag controls the refactoring of NMS to NotificationAttentionHelper"
   bug: "291907312"
 }
+
+flag {
+  name: "cross_app_polite_notifications"
+  namespace: "systemui"
+  description: "This flag controls the cross-app effect of polite notifications"
+  bug: "270456865"
+}
+
+flag {
+  name: "vibrate_while_unlocked"
+  namespace: "systemui"
+  description: "This flag controls the vibrate while unlocked setting of polite notifications"
+  bug: "270456865"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c36c8ca..bd725ed 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -477,7 +477,7 @@
     @NonNull
     List<ApplicationInfo> getInstalledApplications(
             @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
-            int callingUid);
+            int callingUid, boolean forceAllowCrossUser);
 
     @Nullable
     ProviderInfo resolveContentProvider(@NonNull String name,
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2ae1005..2dc3fb7 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4640,7 +4640,7 @@
     @Override
     public List<ApplicationInfo> getInstalledApplications(
             @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
-            int callingUid) {
+            int callingUid, boolean forceAllowCrossUser) {
         if (getInstantAppPackageName(callingUid) != null) {
             return Collections.emptyList();
         }
@@ -4650,12 +4650,14 @@
         final boolean listApex = (flags & MATCH_APEX) != 0;
         final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
 
-        enforceCrossUserPermission(
-                callingUid,
-                userId,
-                false /* requireFullPermission */,
-                false /* checkShell */,
-                "get installed application info");
+        if (!forceAllowCrossUser) {
+            enforceCrossUserPermission(
+                    callingUid,
+                    userId,
+                    false /* requireFullPermission */,
+                    false /* checkShell */,
+                    "get installed application info");
+        }
 
         ArrayList<ApplicationInfo> list;
         final ArrayMap<String, ? extends PackageStateInternal> packageStates =
diff --git a/services/core/java/com/android/server/pm/DeletePackageAction.java b/services/core/java/com/android/server/pm/DeletePackageAction.java
index 8ef6601..31544d5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageAction.java
+++ b/services/core/java/com/android/server/pm/DeletePackageAction.java
@@ -16,17 +16,19 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
 import android.os.UserHandle;
 
 final class DeletePackageAction {
     public final PackageSetting mDeletingPs;
     public final PackageSetting mDisabledPs;
+    @NonNull
     public final PackageRemovedInfo mRemovedInfo;
     public final int mFlags;
     public final UserHandle mUser;
 
     DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs,
-            PackageRemovedInfo removedInfo, int flags, UserHandle user) {
+            @NonNull PackageRemovedInfo removedInfo, int flags, UserHandle user) {
         mDeletingPs = deletingPs;
         mDisabledPs = disabledPs;
         mRemovedInfo = removedInfo;
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 93836266..dcf921c 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -370,7 +370,7 @@
     @GuardedBy("mPm.mInstallLock")
     public boolean deletePackageLIF(@NonNull String packageName, UserHandle user,
             boolean deleteCodeAndResources, @NonNull int[] allUserHandles, int flags,
-            PackageRemovedInfo outInfo, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
         final DeletePackageAction action;
         synchronized (mPm.mLock) {
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
@@ -410,8 +410,8 @@
      * deleted, {@code null} otherwise.
      */
     @Nullable
-    public static DeletePackageAction mayDeletePackageLocked(
-            PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs,
+    public static DeletePackageAction mayDeletePackageLocked(@NonNull PackageRemovedInfo outInfo,
+            PackageSetting ps, @Nullable PackageSetting disabledPs,
             int flags, UserHandle user) {
         if (ps == null) {
             return null;
@@ -460,12 +460,18 @@
         }
 
         final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
-        if (outInfo != null) {
-            // Remember which users are affected, before the installed states are modified
-            outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
-                    ? ps.queryUsersInstalledOrHasData(allUserHandles)
-                    : new int[]{userId};
-        }
+        // Remember which users are affected, before the installed states are modified
+        outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
+                ? ps.queryUsersInstalledOrHasData(allUserHandles)
+                : new int[]{userId};
+        outInfo.populateBroadcastUsers(ps);
+        outInfo.mDataRemoved = (flags & PackageManager.DELETE_KEEP_DATA) == 0;
+        outInfo.mRemovedPackage = ps.getPackageName();
+        outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
+        outInfo.mIsStaticSharedLib =
+                ps.getPkg() != null && ps.getPkg().getStaticSharedLibraryName() != null;
+        outInfo.mIsExternal = ps.isExternalStorage();
+        outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
 
         if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
                 && userId != UserHandle.USER_ALL) {
@@ -503,7 +509,8 @@
                 }
             }
             if (clearPackageStateAndReturn) {
-                mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+                mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags);
+                outInfo.mRemovedAppId = ps.getAppId();
                 mPm.scheduleWritePackageRestrictions(user);
                 return;
             }
@@ -529,12 +536,8 @@
 
         // If the package removed had SUSPEND_APPS, unset any restrictions that might have been in
         // place for all affected users.
-        int[] affectedUserIds = (outInfo != null) ? outInfo.mRemovedUsers : null;
-        if (affectedUserIds == null) {
-            affectedUserIds = mPm.resolveUserIds(userId);
-        }
         final Computer snapshot = mPm.snapshotComputer();
-        for (final int affectedUserId : affectedUserIds) {
+        for (final int affectedUserId : outInfo.mRemovedUsers) {
             if (hadSuspendAppsPermission.get(affectedUserId)) {
                 mPm.unsuspendForSuspendingPackage(snapshot, packageName, affectedUserId);
                 mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId);
@@ -542,24 +545,20 @@
         }
 
         // Take a note whether we deleted the package for all users
-        if (outInfo != null) {
-            synchronized (mPm.mLock) {
-                outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null;
-            }
+        synchronized (mPm.mLock) {
+            outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null;
         }
     }
 
     @GuardedBy("mPm.mInstallLock")
     private void deleteInstalledPackageLIF(PackageSetting ps,
             boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
-            PackageRemovedInfo outInfo, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
         synchronized (mPm.mLock) {
-            if (outInfo != null) {
-                outInfo.mUid = ps.getAppId();
-                outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                        mPm.snapshotComputer(), ps, allUserHandles,
-                        mPm.mSettings.getPackagesLocked());
-            }
+            outInfo.mUid = ps.getAppId();
+            outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+                    mPm.snapshotComputer(), ps, allUserHandles,
+                    mPm.mSettings.getPackagesLocked());
         }
 
         // Delete package data from internal structures and also remove data if flag is set
@@ -567,7 +566,7 @@
                 ps, allUserHandles, outInfo, flags, writeSettings);
 
         // Delete application code and resources only for parent packages
-        if (deleteCodeAndResources && (outInfo != null)) {
+        if (deleteCodeAndResources) {
             outInfo.mArgs = new InstallArgs(
                     ps.getPathString(), getAppDexInstructionSets(
                             ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy()));
@@ -639,7 +638,7 @@
         int flags = action.mFlags;
         final PackageSetting deletedPs = action.mDeletingPs;
         final PackageRemovedInfo outInfo = action.mRemovedInfo;
-        final boolean applyUserRestrictions = outInfo != null && (outInfo.mOrigUsers != null);
+        final boolean applyUserRestrictions = outInfo.mOrigUsers != null;
         final AndroidPackage deletedPkg = deletedPs.getPkg();
         // Confirm if the system package has been updated
         // An updated system app can be deleted. This will also have to restore
@@ -662,10 +661,8 @@
             }
         }
 
-        if (outInfo != null) {
-            // Delete the updated package
-            outInfo.mIsRemovedPackageSystemUpdate = true;
-        }
+        // Delete the updated package
+        outInfo.mIsRemovedPackageSystemUpdate = true;
 
         if (disabledPs.getVersionCode() < deletedPs.getVersionCode()
                 || disabledPs.getAppId() != deletedPs.getAppId()) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 9a0306b..24a33f1 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -476,7 +476,8 @@
             @PackageManager.ApplicationInfoFlagsBits long flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         return new ParceledListSlice<>(
-                snapshot().getInstalledApplications(flags, userId, callingUid));
+                snapshot().getInstalledApplications(flags, userId, callingUid,
+                        /* forceAllowCrossUser= */ false));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 83a6f10..f1c0627 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4126,7 +4126,7 @@
                         null /* request */)) {
                     mDeletePackageHelper.deletePackageLIF(
                             parsedPackage.getPackageName(), null, true,
-                            mPm.mUserManager.getUserIds(), 0, null, false);
+                            mPm.mUserManager.getUserIds(), 0, new PackageRemovedInfo(), false);
                 }
             } else if (newPkgVersionGreater || newSharedUserSetting) {
                 // The application on /system is newer than the application on /data.
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b80c009..58c1c05 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -769,6 +769,9 @@
         @NonNull
         private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp(
                 @Nullable String packageName, UserHandle user) {
+            if (!canAccessProfile(user.getIdentifier(), "Cannot retrieve activities")) {
+                return List.of();
+            }
             List<ApplicationInfo> applicationInfoList =
                     (packageName == null)
                             ? getApplicationInfoListForAllArchivedApps(user)
@@ -827,7 +830,7 @@
         private List<ApplicationInfo> getApplicationInfoListForAllArchivedApps(UserHandle user) {
             final int callingUid = injectBinderCallingUid();
             List<ApplicationInfo> installedApplicationInfoList =
-                    mPackageManagerInternal.getInstalledApplications(
+                    mPackageManagerInternal.getInstalledApplicationsCrossUser(
                             PackageManager.MATCH_ARCHIVED_PACKAGES,
                             user.getIdentifier(),
                             callingUid);
@@ -845,11 +848,12 @@
         private List<ApplicationInfo> getApplicationInfoForArchivedApp(
                 @NonNull String packageName, UserHandle user) {
             final int callingUid = injectBinderCallingUid();
-            ApplicationInfo applicationInfo = mPackageManagerInternal.getApplicationInfo(
-                    packageName,
-                    PackageManager.MATCH_ARCHIVED_PACKAGES,
-                    callingUid,
-                    user.getIdentifier());
+            ApplicationInfo applicationInfo = Binder.withCleanCallingIdentity(() ->
+                    mPackageManagerInternal.getApplicationInfo(
+                            packageName,
+                            PackageManager.MATCH_ARCHIVED_PACKAGES,
+                            callingUid,
+                            user.getIdentifier()));
             if (applicationInfo == null || !applicationInfo.isArchived) {
                 return Collections.EMPTY_LIST;
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index b281808..1e7d043 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -97,7 +97,16 @@
     @Deprecated
     public final List<ApplicationInfo> getInstalledApplications(
             @PackageManager.ApplicationInfoFlagsBits long flags, int userId, int callingUid) {
-        return snapshot().getInstalledApplications(flags, userId, callingUid);
+        return snapshot().getInstalledApplications(flags, userId, callingUid,
+                /* forceAllowCrossUser= */ false);
+    }
+
+    @Override
+    @Deprecated
+    public final List<ApplicationInfo> getInstalledApplicationsCrossUser(
+            @PackageManager.ApplicationInfoFlagsBits long flags, int userId, int callingUid) {
+        return snapshot().getInstalledApplications(flags, userId, callingUid,
+                /* forceAllowCrossUser= */ true);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2880f84..c5b006c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3044,6 +3044,7 @@
         }
     }
 
+    @NonNull
     int[] resolveUserIds(int userId) {
         return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId };
     }
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 52b3131..109d7ba 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -252,8 +252,7 @@
         }
     }
 
-    public void clearPackageStateForUserLIF(PackageSetting ps, int userId,
-            PackageRemovedInfo outInfo, int flags) {
+    public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) {
         final AndroidPackage pkg;
         final SharedUserSetting sus;
         synchronized (mPm.mLock) {
@@ -287,25 +286,12 @@
         }
         mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
                 sharedUserPkgs, userId);
-
-        if (outInfo != null) {
-            if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
-                outInfo.mDataRemoved = true;
-            }
-            outInfo.mRemovedPackage = ps.getPackageName();
-            outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
-            outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
-            outInfo.mRemovedAppId = ps.getAppId();
-            outInfo.mBroadcastUsers = outInfo.mRemovedUsers;
-            outInfo.mIsExternal = ps.isExternalStorage();
-            outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
-        }
     }
 
     // Called to clean up disabled system packages
     public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles) {
         synchronized (mPm.mInstallLock) {
-            removePackageDataLIF(deletedPs, allUserHandles, /* outInfo= */ null,
+            removePackageDataLIF(deletedPs, allUserHandles, new PackageRemovedInfo(),
                     /* flags= */ 0, /* writeSettings= */ false);
         }
     }
@@ -318,20 +304,11 @@
      */
     @GuardedBy("mPm.mInstallLock")
     public void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles,
-            PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = deletedPs.getPackageName();
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
         // Retrieve object to delete permissions for shared user later on
         final AndroidPackage deletedPkg = deletedPs.getPkg();
-        if (outInfo != null) {
-            outInfo.mRemovedPackage = packageName;
-            outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName;
-            outInfo.mIsStaticSharedLib = deletedPkg != null
-                    && deletedPkg.getStaticSharedLibraryName() != null;
-            outInfo.populateBroadcastUsers(deletedPs);
-            outInfo.mIsExternal = deletedPs.isExternalStorage();
-            outInfo.mRemovedPackageVersionCode = deletedPs.getVersionCode();
-        }
 
         removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
         if (!deletedPs.isSystem()) {
@@ -355,9 +332,6 @@
             mAppDataHelper.destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL,
                     FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
             mAppDataHelper.destroyAppProfilesLIF(resolvedPkg.getPackageName());
-            if (outInfo != null) {
-                outInfo.mDataRemoved = true;
-            }
         }
 
         int removedAppId = -1;
@@ -373,9 +347,8 @@
                 mPm.mAppsFilter.removePackage(snapshot,
                         snapshot.getPackageStateInternal(packageName));
                 removedAppId = mPm.mSettings.removePackageLPw(packageName);
-                if (outInfo != null) {
-                    outInfo.mRemovedAppId = removedAppId;
-                }
+                outInfo.mRemovedAppId = removedAppId;
+
                 if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
                     // If we don't have a disabled system package to reinstall, the package is
                     // really gone and its permission state should be removed.
@@ -403,8 +376,8 @@
                     mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
                 });
             }
-        } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
-                && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) {
+        } else if (!deletedPs.isSystem() && !outInfo.mIsUpdate
+                && outInfo.mRemovedUsers != null && !deletedPs.isExternalStorage()) {
             // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false
             // for affected users. This does not apply to app updates where the old apk is replaced
             // but the old data remains.
@@ -424,7 +397,7 @@
         // make sure to preserve per-user installed state if this removal was just
         // a downgrade of a system app to the factory package
         boolean installedStateChanged = false;
-        if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) {
+        if (outInfo.mOrigUsers != null && deletedPs.isSystem()) {
             if (DEBUG_REMOVE) {
                 Slog.d(TAG, "Propagating install state across downgrade");
             }
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 94495bf..ec8af2e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -731,7 +731,7 @@
                                 ? PackageManager.DELETE_KEEP_DATA : 0;
                         synchronized (mPm.mInstallLock) {
                             mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true,
-                                    mPm.mUserManager.getUserIds(), flags, null,
+                                    mPm.mUserManager.getUserIds(), flags, new PackageRemovedInfo(),
                                     true);
                         }
                     }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 70aa19a..b607502 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -256,13 +256,12 @@
 
                     final AndroidPackage pkg = ps.getPkg();
                     final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                    final PackageRemovedInfo outInfo = new PackageRemovedInfo();
 
                     try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(),
                              UserHandle.USER_ALL, deleteFlags,
                             "unloadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER)) {
                         if (mDeletePackageHelper.deletePackageLIF(ps.getPackageName(), null, false,
-                                userIds, deleteFlags, outInfo, false)) {
+                                userIds, deleteFlags, new PackageRemovedInfo(), false)) {
                             unloaded.add(pkg);
                         } else {
                             Slog.w(TAG, "Failed to unload " + ps.getPath());
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 43fd15d..6fbbc0f 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.os.BatteryConsumer;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
index 5fd8ddf..7feb964 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
similarity index 99%
rename from core/java/com/android/internal/os/MultiStateStats.java
rename to services/core/java/com/android/server/power/stats/MultiStateStats.java
index ecfed53..9356950 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.os;
+package com.android.server.power.stats;
 
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 0facb9c..1637022 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index abfe9de..e1eb8f0 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -20,7 +20,6 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.PersistableBundle;
-import android.util.FastImmutableArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -30,8 +29,9 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
-import java.util.stream.Stream;
 
 /**
  * Collects snapshots of power-related system statistics.
@@ -246,8 +246,7 @@
 
     @GuardedBy("this")
     @SuppressWarnings("unchecked")
-    private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList =
-            new FastImmutableArraySet<Consumer<PowerStats>>(new Consumer[0]);
+    private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
 
     public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
         mHandler = handler;
@@ -262,9 +261,13 @@
     @SuppressWarnings("unchecked")
     public void addConsumer(Consumer<PowerStats> consumer) {
         synchronized (this) {
-            mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>(
-                    Stream.concat(mConsumerList.stream(), Stream.of(consumer))
-                            .toArray(Consumer[]::new));
+            if (mConsumerList.contains(consumer)) {
+                return;
+            }
+
+            List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
+            newList.add(consumer);
+            mConsumerList = Collections.unmodifiableList(newList);
         }
     }
 
@@ -275,9 +278,9 @@
     @SuppressWarnings("unchecked")
     public void removeConsumer(Consumer<PowerStats> consumer) {
         synchronized (this) {
-            mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>(
-                    mConsumerList.stream().filter(c -> c != consumer)
-                            .toArray(Consumer[]::new));
+            List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
+            newList.remove(consumer);
+            mConsumerList = Collections.unmodifiableList(newList);
         }
     }
 
@@ -302,8 +305,9 @@
         if (stats == null) {
             return;
         }
-        for (Consumer<PowerStats> consumer : mConsumerList) {
-            consumer.accept(stats);
+        List<Consumer<PowerStats>> consumerList = mConsumerList;
+        for (int i = consumerList.size() - 1; i >= 0; i--) {
+            consumerList.get(i).accept(stats);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 1f6f113..c267b79 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -22,7 +22,6 @@
 import android.os.UidBatteryConsumer;
 import android.util.Slog;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
index 97d872a..121a98b 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -18,7 +18,6 @@
 
 import android.annotation.DurationMillisLong;
 import android.app.AlarmManager;
-import android.content.Context;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.util.IndentingPrintWriter;
@@ -30,6 +29,7 @@
 import java.io.PrintWriter;
 import java.util.Calendar;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 /**
  * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
@@ -39,7 +39,7 @@
     private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
     private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
 
-    private final Context mContext;
+    private final AlarmScheduler mAlarmScheduler;
     private boolean mEnablePeriodicPowerStatsCollection;
     @DurationMillisLong
     private final long mAggregatedPowerStatsSpanDuration;
@@ -49,24 +49,38 @@
     private final Clock mClock;
     private final MonotonicClock mMonotonicClock;
     private final Handler mHandler;
-    private final BatteryStatsImpl mBatteryStats;
+    private final Runnable mPowerStatsCollector;
+    private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs;
     private final PowerStatsAggregator mPowerStatsAggregator;
     private long mLastSavedSpanEndMonotonicTime;
 
-    public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+    /**
+     * External dependency on AlarmManager.
+     */
+    public interface AlarmScheduler {
+        /**
+         * Should use AlarmManager to schedule an inexact, non-wakeup alarm.
+         */
+        void scheduleAlarm(long triggerAtMillis, String tag,
+                AlarmManager.OnAlarmListener onAlarmListener, Handler handler);
+    }
+
+    public PowerStatsScheduler(Runnable powerStatsCollector,
+            PowerStatsAggregator powerStatsAggregator,
             @DurationMillisLong long aggregatedPowerStatsSpanDuration,
             @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
-            Clock clock, MonotonicClock monotonicClock, Handler handler,
-            BatteryStatsImpl batteryStats) {
-        mContext = context;
+            AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock,
+            Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) {
         mPowerStatsAggregator = powerStatsAggregator;
         mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
         mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
         mPowerStatsStore = powerStatsStore;
+        mAlarmScheduler = alarmScheduler;
         mClock = clock;
         mMonotonicClock = monotonicClock;
         mHandler = handler;
-        mBatteryStats = batteryStats;
+        mPowerStatsCollector = powerStatsCollector;
+        mEarliestAvailableBatteryHistoryTimeMs = earliestAvailableBatteryHistoryTimeMs;
     }
 
     /**
@@ -81,9 +95,8 @@
     }
 
     private void scheduleNextPowerStatsAggregation() {
-        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
-        alarmManager.set(AlarmManager.ELAPSED_REALTIME,
-                mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+        mAlarmScheduler.scheduleAlarm(mClock.elapsedRealtime() + mPowerStatsAggregationPeriod,
+                "PowerStats",
                 () -> {
                     schedulePowerStatsAggregation();
                     mHandler.post(this::scheduleNextPowerStatsAggregation);
@@ -96,7 +109,7 @@
     @VisibleForTesting
     public void schedulePowerStatsAggregation() {
         // Catch up the power stats collectors
-        mBatteryStats.schedulePowerStatsSampleCollection();
+        mPowerStatsCollector.run();
         mHandler.post(this::aggregateAndStorePowerStats);
     }
 
@@ -105,7 +118,7 @@
         long currentMonotonicTime = mMonotonicClock.monotonicTime();
         long startTime = getLastSavedSpanEndMonotonicTime();
         if (startTime < 0) {
-            startTime = mBatteryStats.getHistory().getStartTime();
+            startTime = mEarliestAvailableBatteryHistoryTimeMs.get();
         }
         long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
                 mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e4c7fc1..6f27507 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,14 +1851,14 @@
                             sessionState.currentChannel = channelUri;
                             notifyCurrentChannelInfosUpdatedLocked(userState);
                             if (!sessionState.isRecordingSession) {
-                                String actualInputId = getActualInputId(sessionState);
-                                if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                                String sessionActualInputId = getSessionActualInputId(sessionState);
+                                if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                     logExternalInputEvent(
                                             FrameworkStatsLog
                                                     .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                            actualInputId, sessionState);
+                                            sessionActualInputId, sessionState);
                                 }
-                                mOnScreenInputId = actualInputId;
+                                mOnScreenInputId = sessionActualInputId;
                                 mOnScreenSessionState = sessionState;
                             }
                         }
@@ -2985,11 +2985,20 @@
     // e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI
     // session should be the input id of CEC device instead of the default HDMI input id.
     @GuardedBy("mLock")
-    private String getActualInputId(SessionState sessionState) {
+    private String getSessionActualInputId(SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(sessionState.inputId);
+        if (tvInputState == null) {
+            Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId);
+            return sessionState.inputId;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
-        String actualInputId = sessionState.inputId;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId);
+            return sessionState.inputId;
+        }
+
+        String sessionActualInputId = sessionState.inputId;
         switch (tvInputInfo.getType()) {
             case TvInputInfo.TYPE_HDMI:
                 // TODO: find a better approach towards active CEC device in future
@@ -2997,13 +3006,13 @@
                         mTvInputHardwareManager.getHdmiParentInputMap();
                 if (hdmiParentInputMap.containsKey(sessionState.inputId)) {
                     List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId);
-                    actualInputId = parentInputList.get(0);
+                    sessionActualInputId = parentInputList.get(0);
                 }
                 break;
             default:
                 break;
         }
-        return actualInputId;
+        return sessionActualInputId;
     }
 
     @Nullable
@@ -3111,7 +3120,21 @@
     private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(inputId);
+        if (tvInputState == null) {
+            Slog.w(TAG, "Cannot find input state for input id " + inputId);
+            // If input id is not found, try to find the input id of this sessionState.
+            inputId = sessionState.inputId;
+            tvInputState = userState.inputMap.get(inputId);
+        }
+        if (tvInputState == null) {
+            Slog.w(TAG, "Cannot find input state for sessionState.inputId " + inputId);
+            return;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + inputId);
+            return;
+        }
         int inputState = tvInputState.state;
         int inputType = tvInputInfo.getType();
         String displayName = tvInputInfo.loadLabel(mContext).toString();
@@ -3647,14 +3670,14 @@
                         mSessionState.currentChannel = channelUri;
                         notifyCurrentChannelInfosUpdatedLocked(userState);
                         if (!mSessionState.isRecordingSession) {
-                            String actualInputId = getActualInputId(mSessionState);
-                            if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                            String sessionActualInputId = getSessionActualInputId(mSessionState);
+                            if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                 logExternalInputEvent(
                                         FrameworkStatsLog
                                                 .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                        actualInputId, mSessionState);
+                                        sessionActualInputId, mSessionState);
                             }
-                            mOnScreenInputId = actualInputId;
+                            mOnScreenInputId = sessionActualInputId;
                             mOnScreenSessionState = mSessionState;
                         }
                     }
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 2b6dffb..7b5192c 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -16,37 +16,22 @@
 
 package com.android.server.utils;
 
-import static android.text.TextUtils.formatSimple;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
-import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.util.RingBuffer;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
@@ -102,12 +87,6 @@
     private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     /**
-     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
-     * used to diagnose long latencies in the client.
-     */
-    private static final boolean ENABLE_TRACING = false;
-
-    /**
      * Return true if the feature is enabled.  By default, the value is take from the Flags class
      * but it can be changed for local testing.
      */
@@ -116,22 +95,13 @@
     }
 
     /**
-     * The status of an ANR timer.  TIMER_INVALID status is returned when an error is detected.
+     * This class allows test code to provide instance-specific overrides.
      */
-    private static final int TIMER_INVALID = 0;
-    private static final int TIMER_RUNNING = 1;
-    private static final int TIMER_EXPIRED = 2;
-
-    @IntDef(prefix = { "TIMER_" }, value = {
-                TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
-            })
-    private @interface TimerStatus {}
-
-    /**
-     * A static list of all known AnrTimer instances, used for dumping and testing.
-     */
-    @GuardedBy("sAnrTimerList")
-    private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+    static class Injector {
+        boolean anrTimerServiceEnabled() {
+            return AnrTimer.anrTimerServiceEnabled();
+        }
+    }
 
     /**
      * An error is defined by its issue, the operation that detected the error, the tag of the
@@ -161,6 +131,22 @@
             this.arg = arg;
             this.timestamp = SystemClock.elapsedRealtime();
         }
+
+        /**
+         * Dump a single error to the output stream.
+         */
+        private void dump(IndentingPrintWriter ipw, int seq) {
+            ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg);
+
+            final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+            final long etime = offset + timestamp;
+            ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
+            ipw.increaseIndent();
+            for (int i = 0; i < stack.length; i++) {
+                ipw.println("    " + stack[i].toString());
+            }
+            ipw.decreaseIndent();
+        }
     }
 
     /**
@@ -171,132 +157,10 @@
     @GuardedBy("sErrors")
     private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20);
 
-    /**
-     * A record of a single anr timer.  The pid and uid are retained for reference but they do not
-     * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
-     * through the owner field.  Access to timer fields is guarded by the mLock of the owner.
-     */
-    private static class Timer {
-        /** The AnrTimer that is managing this Timer. */
-        final AnrTimer owner;
-
-        /** The argument that uniquely identifies the Timer in the context of its current owner. */
-        final Object arg;
-        /** The pid of the process being tracked by this Timer. */
-        final int pid;
-        /** The uid of the process being tracked by this Timer as reported by the kernel. */
-        final int uid;
-        /** The original timeout. */
-        final long timeoutMs;
-
-        /** The status of the Timer.  */
-        @GuardedBy("owner.mLock")
-        @TimerStatus
-        int status;
-
-        /** The absolute time the timer was startd */
-        final long startedMs;
-
-        /** Fields used by the native timer service. */
-
-        /** The timer ID: used to exchange information with the native service. */
-        int timerId;
-
-        /** Fields used by the legacy timer service. */
-
-        /**
-         * The process's cpu delay time when the timer starts . It is meaningful only if
-         * extendable is true.  The cpu delay is cumulative, so the incremental delay that occurs
-         * during a timer is the delay at the end of the timer minus this value.  Units are in
-         * milliseconds.
-         */
-        @GuardedBy("owner.mLock")
-        long initialCpuDelayMs;
-
-        /** True if the timer has been extended. */
-        @GuardedBy("owner.mLock")
-        boolean extended;
-
-        /**
-         * Fetch a new Timer.  This is private.  Clients should get a new timer using the obtain()
-         * method.
-         */
-        private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
-                @NonNull AnrTimer service) {
-            this.arg = arg;
-            this.pid = pid;
-            this.uid = uid;
-            this.timerId = 0;
-            this.timeoutMs = timeoutMs;
-            this.startedMs = now();
-            this.owner = service;
-            this.initialCpuDelayMs = 0;
-            this.extended = false;
-            this.status = TIMER_INVALID;
-        }
-
-        /** Get a timer.  This implementation constructs a new timer. */
-        static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
-                @NonNull AnrTimer service) {
-            return new Timer(pid, uid, arg, timeout, service);
-        }
-
-        /** Release a timer. This implementation simply drops the timer. */
-        void release() {
-        }
-
-        /** Return the age of the timer. This is used for debugging. */
-        long age() {
-            return now() - startedMs;
-        }
-
-        /**
-         * The hash code is generated from the owner and the argument.  By definition, the
-         * combination must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public int hashCode() {
-            return Objects.hash(owner, arg);
-        }
-
-        /**
-         * The equality check compares the owner and the argument.  By definition, the combination
-         * must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public boolean equals(Object r) {
-            if (r instanceof Timer) {
-                Timer t = (Timer) r;
-                return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            final int myStatus;
-            synchronized (owner.mLock) {
-                myStatus = status;
-            }
-            return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
-                    + " " + statusString(myStatus) + " " + owner.mLabel;
-        }
-    }
-
     /** A lock for the AnrTimer instance. */
     private final Object mLock = new Object();
 
     /**
-     * The map from client argument to the associated timer.
-     */
-    @GuardedBy("mLock")
-    private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
-
-    /** The highwater mark of started, but not closed, timers. */
-    @GuardedBy("mLock")
-    private int mMaxStarted = 0;
-
-    /**
      * The total number of timers started.
      */
     @GuardedBy("mLock")
@@ -309,176 +173,6 @@
     private int mTotalErrors = 0;
 
     /**
-     * The total number of timers that have expired.
-     */
-    @GuardedBy("mLock")
-    private int mTotalExpired = 0;
-
-    /**
-     * A TimerService that generates a timeout event <n> milliseconds in the future.  See the
-     * class documentation for an explanation of the operations.
-     */
-    private abstract class TimerService {
-        /** Start a timer.  The timeout must be initialized. */
-        abstract boolean start(@NonNull Timer timer);
-
-        abstract void cancel(@NonNull Timer timer);
-
-        abstract void accept(@NonNull Timer timer);
-
-        abstract void discard(@NonNull Timer timer);
-    }
-
-    /**
-     * A class to assist testing.  All methods are null by default but can be overridden as
-     * necessary for a test.
-     */
-    @VisibleForTesting
-    static class Injector {
-        private final Handler mReferenceHandler;
-
-        Injector(@NonNull Handler handler) {
-            mReferenceHandler = handler;
-        }
-
-        /**
-         * Return a handler for the given Callback, based on the reference handler. The handler
-         * might be mocked, in which case it does not have a valid Looper.  In this case, use the
-         * main Looper.
-         */
-        @NonNull
-        Handler newHandler(@NonNull Handler.Callback callback) {
-            Looper looper = mReferenceHandler.getLooper();
-            if (looper == null) looper = Looper.getMainLooper();
-            return new Handler(looper, callback);
-        }
-
-        /**
-         * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
-         * for unit tests.
-         **/
-        @NonNull
-        CpuTracker newTracker() {
-            return new CpuTracker();
-        }
-
-        /** Return true if the feature is enabled. */
-        boolean isFeatureEnabled() {
-            return anrTimerServiceEnabled();
-        }
-    }
-
-    /**
-     * A helper class to measure CPU delays.  Given a process ID, this class will return the
-     * cumulative CPU delay for the PID, since process inception.  This class is defined to assist
-     * testing.
-     */
-    @VisibleForTesting
-    static class CpuTracker {
-        /**
-         * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
-         * single process and not on the collection of threads associated with that process.
-         */
-        private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
-
-        /** A simple wrapper to fetch the delay.  This method can be overridden for testing. */
-        long delay(int pid) {
-            return mCpu.getCpuDelayTimeForPid(pid);
-        }
-    }
-
-    /**
-     * The "user-space" implementation of the timer service.  This service uses its own message
-     * handler to create timeouts.
-     */
-    private class HandlerTimerService extends TimerService {
-        /** The lock for this handler */
-        private final Object mLock = new Object();
-
-        /** The message handler for scheduling future events. */
-        private final Handler mHandler;
-
-        /** The interface to fetch process statistics that might extend an ANR timeout. */
-        private final CpuTracker mCpu;
-
-        /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
-        @VisibleForTesting
-        HandlerTimerService(@NonNull Injector injector) {
-            mHandler = injector.newHandler(this::expires);
-            mCpu = injector.newTracker();
-        }
-
-        /** Post a message with the specified timeout.  The timer is not modified. */
-        private void post(@NonNull Timer t, long timeoutMillis) {
-            final Message msg = mHandler.obtainMessage();
-            msg.obj = t;
-            mHandler.sendMessageDelayed(msg, timeoutMillis);
-        }
-
-        /**
-         * The local expiration handler first attempts to compute a timer extension.  If the timer
-         * should be extended, it is rescheduled in the future (granting more time to the
-         * associated process).  If the timer should not be extended then the timeout is delivered
-         * to the client.
-         *
-         * A process is extended to account for the time the process was swapped out and was not
-         * runnable through no fault of its own.  A timer can only be extended once and only if
-         * the AnrTimer permits extensions.  Finally, a timer will never be extended by more than
-         * the original timeout, so the total timeout will never be more than twice the originally
-         * configured timeout.
-         */
-        private boolean expires(Message msg) {
-            Timer t = (Timer) msg.obj;
-            synchronized (mLock) {
-                long extension = 0;
-                if (mExtend && !t.extended) {
-                    extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
-                    if (extension < 0) extension = 0;
-                    if (extension > t.timeoutMs) extension = t.timeoutMs;
-                    t.extended = true;
-                }
-                if (extension > 0) {
-                    post(t, extension);
-                } else {
-                    onExpiredLocked(t);
-                }
-            }
-            return true;
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        boolean start(@NonNull Timer t) {
-            if (mExtend) {
-                t.initialCpuDelayMs = mCpu.delay(t.pid);
-            }
-            post(t, t.timeoutMs);
-            return true;
-        }
-
-        @Override
-        void cancel(@NonNull Timer t) {
-            mHandler.removeMessages(0, t);
-        }
-
-        @Override
-        void accept(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        @Override
-        void discard(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        /** The string identifies this subclass of AnrTimerService as being based on handlers. */
-        @Override
-        public String toString() {
-            return "handler";
-        }
-    }
-
-    /**
      * The handler for messages sent from this instance.
      */
     private final Handler mHandler;
@@ -499,18 +193,6 @@
     private final boolean mExtend;
 
     /**
-     * The timer service to use for this AnrTimer.
-     */
-    private final TimerService mTimerService;
-
-    /**
-     * Whether or not canceling a non-existent timer is an error.  Clients often cancel freely
-     * preemptively, without knowing if the timer was ever started.  Keeping this variable true
-     * means that such behavior is not an error.
-     */
-    private final boolean mLenientCancel = true;
-
-    /**
      * The top-level switch for the feature enabled or disabled.
      */
     private final FeatureSwitch mFeature;
@@ -528,40 +210,34 @@
      * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
      * the client.  The extension policy is not part of the instance.
      *
-     * This method accepts an {@link #Injector} to tune behavior for testing.  This method should
-     * not be called directly by regular clients.
-     *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
      * @param label A name for this instance.
      * @param extend A flag to indicate if expired timers can be granted extensions.
-     * @param injector An {@link #Injector} to tune behavior for testing.
+     * @param injector An injector to provide overrides for testing.
      */
     @VisibleForTesting
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
-            @NonNull Injector injector) {
+             @NonNull Injector injector) {
         mHandler = handler;
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        boolean enabled = injector.isFeatureEnabled();
-        if (!enabled) {
-            mFeature = new FeatureDisabled();
-            mTimerService = null;
-        } else {
-            mFeature = new FeatureEnabled();
-            mTimerService = new HandlerTimerService(injector);
-
-            synchronized (sAnrTimerList) {
-                sAnrTimerList.add(new WeakReference(this));
-            }
-        }
-        Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label));
+        mFeature = new FeatureDisabled();
     }
 
     /**
-     * Create an AnrTimer instance with the default {@link #Injector}.  See {@link AnrTimer(Handler,
-     * int, String, boolean, Injector} for a functional description.
+     * Create one AnrTimer instance.  The instance is given a handler and a "what".  Individual
+     * timers are started with {@link #start}.  If a timer expires, then a {@link Message} is sent
+     * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+     * to the timer key.
+     *
+     * AnrTimer instances have a label, which must be unique.  The label is used for reporting and
+     * debug.
+     *
+     * If an individual timer expires internally, and the "extend" parameter is true, then the
+     * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+     * the client.  The extension policy is not part of the instance.
      *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
@@ -569,7 +245,7 @@
      * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
-        this(handler, what, label, extend, new Injector(handler));
+        this(handler, what, label, extend, new Injector());
     }
 
     /**
@@ -596,105 +272,17 @@
     }
 
     /**
-     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
-     */
-    private void traceBegin(Timer t, String what) {
-        if (ENABLE_TRACING) {
-            final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
-        }
-    }
-
-    /**
-     * End a trace on the timer.
-     */
-    private void traceEnd(Timer t) {
-        if (ENABLE_TRACING) {
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
-        }
-    }
-
-    /**
-     * Return the string representation for a timer status.
-     */
-    private static String statusString(int s) {
-        switch (s) {
-            case TIMER_INVALID: return "invalid";
-            case TIMER_RUNNING: return "running";
-            case TIMER_EXPIRED: return "expired";
-        }
-        return formatSimple("unknown: %d", s);
-    }
-
-    /**
-     * Delete the timer associated with arg from the maps and return it.  Return null if the timer
-     * was not found.
-     */
-    @GuardedBy("mLock")
-    private Timer removeLocked(V arg) {
-        Timer timer = mTimerMap.remove(arg);
-        return timer;
-    }
-
-    /**
-     * Return the number of timers currently running.
-     */
-    @VisibleForTesting
-    static int sizeOfTimerList() {
-        synchronized (sAnrTimerList) {
-            int totalTimers = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) totalTimers += client.mTimerMap.size();
-            }
-            return totalTimers;
-        }
-    }
-
-    /**
-     * Clear out all existing timers.  This will lead to unexpected behavior if used carelessly.
-     * It is available only for testing.  It returns the number of times that were actually
-     * erased.
-     */
-    @VisibleForTesting
-    static int resetTimerListForHermeticTest() {
-        synchronized (sAnrTimerList) {
-            int mapLen = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) {
-                    mapLen += client.mTimerMap.size();
-                    client.mTimerMap.clear();
-                }
-            }
-            if (mapLen > 0) {
-                Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
-            }
-            return mapLen;
-        }
-    }
-
-    /**
-     * Generate a log message for a timer.
-     */
-    private void report(@NonNull Timer timer, @NonNull String msg) {
-        Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
-    }
-
-    /**
      * The FeatureSwitch class provides a quick switch between feature-enabled behavior and
      * feature-disabled behavior.
      */
     private abstract class FeatureSwitch {
-        abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+        abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);
 
-        abstract boolean cancel(@NonNull V arg);
+        abstract void cancel(@NonNull V arg);
 
-        abstract boolean accept(@NonNull V arg);
+        abstract void accept(@NonNull V arg);
 
-        abstract boolean discard(@NonNull V arg);
+        abstract void discard(@NonNull V arg);
 
         abstract boolean enabled();
     }
@@ -706,29 +294,25 @@
     private class FeatureDisabled extends FeatureSwitch {
         /** Start a timer by sending a message to the client's handler. */
         @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Message msg = mHandler.obtainMessage(mWhat, arg);
             mHandler.sendMessageDelayed(msg, timeoutMs);
-            return true;
         }
 
         /** Cancel a timer by removing the message from the client's handler. */
         @Override
-        boolean cancel(@NonNull V arg) {
+        void cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
-            return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
         @Override
-        boolean accept(@NonNull V arg) {
-            return true;
+        void accept(@NonNull V arg) {
         }
 
         /** discard() is a no-op when the feature is disabled. */
         @Override
-        boolean discard(@NonNull V arg) {
-            return true;
+        void discard(@NonNull V arg) {
         }
 
         /** The feature is not enabled. */
@@ -739,113 +323,6 @@
     }
 
     /**
-     * The FeatureEnabled class enables the AnrTimer logic.  It is used when the AnrTimer service
-     * is enabled via Flags.anrTimerServiceEnabled.
-     */
-    private class FeatureEnabled extends FeatureSwitch {
-
-        /**
-         * Start a timer.
-         */
-        @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-            final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
-            synchronized (mLock) {
-                Timer old = mTimerMap.get(arg);
-                // There is an existing timer.  If the timer was running, then cancel the running
-                // timer and restart it.  If the timer was expired record a protocol error and
-                // discard the expired timer.
-                if (old != null) {
-                    if (old.status == TIMER_EXPIRED) {
-                      restartedLocked(old.status, arg);
-                        discard(arg);
-                    } else {
-                        cancel(arg);
-                    }
-                }
-                if (mTimerService.start(timer)) {
-                    timer.status = TIMER_RUNNING;
-                    mTimerMap.put(arg, timer);
-                    mTotalStarted++;
-                    mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
-                    if (DEBUG) report(timer, "start");
-                    return true;
-                } else {
-                    Log.e(TAG, "AnrTimer.start failed");
-                    return false;
-                }
-            }
-        }
-
-        /**
-         * Cancel a timer.  Return false if the timer was not found.
-         */
-        @Override
-        boolean cancel(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    if (!mLenientCancel) notFoundLocked("cancel", arg);
-                    return false;
-                }
-                mTimerService.cancel(timer);
-                // There may be an expiration message in flight.  Cancel it.
-                mHandler.removeMessages(mWhat, arg);
-                if (DEBUG) report(timer, "cancel");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
-         * timeout handler is executing.  Return false if the timer was not found.
-         */
-        @Override
-        boolean accept(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("accept", arg);
-                    return false;
-                }
-                mTimerService.accept(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "accept");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
-         * longer interesting.  No statistics are collected.  Return false if the time was not
-         * found.
-         */
-        @Override
-        boolean discard(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("discard", arg);
-                    return false;
-                }
-                mTimerService.discard(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "discard");
-                timer.release();
-                return true;
-            }
-        }
-
-        /** The feature is enabled. */
-        @Override
-        boolean enabled() {
-            return true;
-        }
-    }
-
-    /**
      * Start a timer associated with arg.  The same object must be used to cancel, accept, or
      * discard a timer later.  If a timer already exists with the same arg, then the existing timer
      * is canceled and a new timer is created.
@@ -854,32 +331,27 @@
      * @param pid The Linux process ID of the target being timed.
      * @param uid The Linux user ID of the target being timed.
      * @param timeoutMs The timer timeout, in milliseconds.
-     * @return true if the timer was successfully created.
      */
-    public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-        return mFeature.start(arg, pid, uid, timeoutMs);
+    public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
      * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
      * expired, the call is treated as a discard.  No errors are reported if the timer does not
      * exist or if the timer has expired.
-     *
-     * @return true if the timer was found and was running.
      */
-    public boolean cancel(@NonNull V arg) {
-        return mFeature.cancel(arg);
+    public void cancel(@NonNull V arg) {
+        mFeature.cancel(arg);
     }
 
     /**
      * Accept the expired timer associated with arg.  This indicates that the caller considers the
      * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
      * an error to accept a running timer, however the running timer will be canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean accept(@NonNull V arg) {
-        return mFeature.accept(arg);
+    public void accept(@NonNull V arg) {
+        mFeature.accept(arg);
     }
 
     /**
@@ -889,24 +361,9 @@
      * such a process could be stopped at a breakpoint and its failure to respond would not be an
      * error.  It is an error to discard a running timer, however the running timer will be
      * canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean discard(@NonNull V arg) {
-        return mFeature.discard(arg);
-    }
-
-    /**
-     * The notifier that a timer has fired.  The timer is not modified.
-     */
-    @GuardedBy("mLock")
-    private void onExpiredLocked(@NonNull Timer timer) {
-        if (DEBUG) report(timer, "expire");
-        traceBegin(timer, "expired");
-        mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
-        synchronized (mLock) {
-            mTotalExpired++;
-        }
+    public void discard(@NonNull V arg) {
+        mFeature.discard(arg);
     }
 
     /**
@@ -916,9 +373,7 @@
         synchronized (mLock) {
             pw.format("timer: %s\n", mLabel);
             pw.increaseIndent();
-            pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
-                    mTotalStarted, mMaxStarted, mTimerMap.size(),
-                    mTotalExpired, mTotalErrors);
+            pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
             pw.decreaseIndent();
         }
     }
@@ -931,10 +386,20 @@
     }
 
     /**
-     * The current time in milliseconds.
+     * Dump all errors to the output stream.
      */
-    private static long now() {
-        return SystemClock.uptimeMillis();
+    private static void dumpErrors(IndentingPrintWriter ipw) {
+        Error errors[];
+        synchronized (sErrors) {
+            if (sErrors.size() == 0) return;
+            errors = sErrors.toArray();
+        }
+        ipw.println("Errors");
+        ipw.increaseIndent();
+        for (int i = 0; i < errors.length; i++) {
+            if (errors[i] != null) errors[i].dump(ipw, i);
+        }
+        ipw.decreaseIndent();
     }
 
     /**
@@ -966,60 +431,12 @@
     }
 
     /**
-     * Log an error about a timer that is started when there is an existing timer.
-     */
-    @GuardedBy("mLock")
-    private void restartedLocked(@TimerStatus int status, Object arg) {
-        recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
-    }
-
-    /**
-     * Dump a single error to the output stream.
-     */
-    private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
-        ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
-                err.issue, err.arg);
-
-        final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
-        final long etime = offset + err.timestamp;
-        ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
-        ipw.increaseIndent();
-        for (int i = 0; i < err.stack.length; i++) {
-            ipw.println("    " + err.stack[i].toString());
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
-     * Dump all errors to the output stream.
-     */
-    private static void dumpErrors(IndentingPrintWriter ipw) {
-        Error errors[];
-        synchronized (sErrors) {
-            if (sErrors.size() == 0) return;
-            errors = sErrors.toArray();
-        }
-        ipw.println("Errors");
-        ipw.increaseIndent();
-        for (int i = 0; i < errors.length; i++) {
-            if (errors[i] != null) dump(ipw, i, errors[i]);
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
      * Dumpsys output.
      */
     public static void dump(@NonNull PrintWriter pw, boolean verbose) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println("AnrTimer statistics");
         ipw.increaseIndent();
-        synchronized (sAnrTimerList) {
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) client.dump(ipw);
-            }
-        }
         if (verbose) dumpErrors(ipw);
         ipw.format("AnrTimerEnd\n");
         ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 8c50748..fc824ab 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -271,8 +271,10 @@
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
-        injector.addService(VIBRATOR_CONTROL_SERVICE,
-                new VibratorControlService(new VibratorControllerHolder(), mLock));
+        if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
+            injector.addService(VIBRATOR_CONTROL_SERVICE,
+                    new VibratorControlService(new VibratorControllerHolder(), mLock));
+        }
 
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0d06f5b..04ac9fb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -562,7 +562,6 @@
     boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
-    boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
     boolean immersive;      // immersive mode (don't interrupt if possible)
     boolean forceNewConfig; // force re-create with new config next time
     boolean supportsEnterPipOnTaskSwitch;  // This flag is set by the system to indicate that the
@@ -1201,8 +1200,6 @@
                 pw.print(" noDisplay="); pw.print(noDisplay);
                 pw.print(" immersive="); pw.print(immersive);
                 pw.print(" launchMode="); pw.println(launchMode);
-        pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
-                pw.print(" forceNewConfig="); pw.println(forceNewConfig);
         pw.print(prefix); pw.print("mActivityType=");
                 pw.println(activityTypeToString(getActivityType()));
         pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput=");
@@ -3848,7 +3845,7 @@
         // updated for restoring original orientation of the display.
         if (next == null) {
             mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
-                    false /* markFrozenIfConfigChanged */, true /* deferResume */);
+                    true /* deferResume */);
         }
         if (activityRemoved) {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -4090,7 +4087,6 @@
         cleanUpSplashScreen();
 
         deferRelaunchUntilPaused = false;
-        frozenBeforeDestroy = false;
 
         if (setState) {
             setState(DESTROYED, "cleanUp");
@@ -6276,7 +6272,6 @@
     }
 
     void handleAlreadyVisible() {
-        stopFreezingScreenLocked(false);
         try {
             if (returningOptions != null) {
                 app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle());
@@ -6710,19 +6705,6 @@
         stopFreezingScreen(true, true);
     }
 
-    void stopFreezingScreenLocked(boolean force) {
-        if (force || frozenBeforeDestroy) {
-            frozenBeforeDestroy = false;
-            if (getParent() == null) {
-                return;
-            }
-            ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Clear freezing of %s: visible=%b freezing=%b", token,
-                                isVisible(), isFreezingScreen());
-            stopFreezingScreen(true, force);
-        }
-    }
-
     void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
         if (!mFreezingScreen) {
             return;
@@ -9569,7 +9551,6 @@
         if (finishing) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter "
                     + "in finishing %s", this);
-            stopFreezingScreenLocked(false);
             return true;
         }
 
@@ -9660,7 +9641,6 @@
         // pick that up next time it starts.
         if (!attachedToProcess()) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
-            stopFreezingScreenLocked(false);
             forceNewConfig = false;
             return true;
         }
@@ -9723,9 +9703,6 @@
         }
         notifyDisplayCompatPolicyAboutConfigurationChange(
                 mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
-
-        stopFreezingScreenLocked(false);
-
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index dfb2a5f..4b0177a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1549,8 +1549,7 @@
         final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
         if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
             mRootWindowContainer.ensureVisibilityAndConfig(
-                    currentTop, currentTop.getDisplayId(),
-                    true /* markFrozenIfConfigChanged */, false /* deferResume */);
+                    currentTop, currentTop.getDisplayId(), false /* deferResume */);
         }
 
         if (!mAvoidMoveToFront && mDoResume && mRootWindowContainer
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4a479aa..e59601c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -843,7 +843,7 @@
                 // We don't want to perform a redundant launch of the same record while ensuring
                 // configurations and trying to resume top activity of focused root task.
                 mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
-                        false /* markFrozenIfConfigChanged */, true /* deferResume */);
+                        true /* deferResume */);
             }
 
             if (mKeyguardController.checkKeyguardVisibility(r) && r.allowMoveToFront()) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4cc1c0d..ae10ce3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -546,10 +546,12 @@
     boolean isDefaultDisplay;
 
     /** Detect user tapping outside of current focused task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     @VisibleForTesting
     final TaskTapPointerEventListener mTapDetector;
 
     /** Detect user tapping outside of current focused root task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     private Region mTouchExcludeRegion = new Region();
 
     /** Save allocating when calculating rects */
@@ -1194,12 +1196,18 @@
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
         mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
-        // Tap Listeners are supported for:
-        // 1. All physical displays (multi-display).
-        // 2. VirtualDisplays on VR, AA (and everything else).
-        mTapDetector = new TaskTapPointerEventListener(mWmService, this);
-        registerPointerEventListener(mTapDetector);
-        registerPointerEventListener(mWmService.mMousePositionTracker);
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            mTapDetector = null;
+        } else {
+            // Tap Listeners are supported for:
+            // 1. All physical displays (multi-display).
+            // 2. VirtualDisplays on VR, AA (and everything else).
+            mTapDetector = new TaskTapPointerEventListener(mWmService, this);
+            registerPointerEventListener(mTapDetector);
+        }
+        if (mWmService.mMousePositionTracker != null) {
+            registerPointerEventListener(mWmService.mMousePositionTracker);
+        }
         if (mWmService.mAtmService.getRecentTasks() != null) {
             registerPointerEventListener(
                     mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -1641,12 +1649,13 @@
         mWmService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
-    void sendNewConfiguration() {
+    /** Returns {@code true} if the display configuration is changed. */
+    boolean sendNewConfiguration() {
         if (!isReady()) {
-            return;
+            return false;
         }
         if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
-            return;
+            return false;
         }
 
         final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting()
@@ -1661,7 +1670,7 @@
             displayConfig.meet();
         }
         if (configUpdated) {
-            return;
+            return true;
         }
 
         // The display configuration doesn't change. If there is a launching transformed app, that
@@ -1679,6 +1688,7 @@
             setLayoutNeeded();
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
+        return false;
     }
 
     @Override
@@ -1700,7 +1710,6 @@
             final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
             final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
                     false /* deferResume */, null /* result */);
-            activityRecord.frozenBeforeDestroy = true;
             if (!kept) {
                 mRootWindowContainer.resumeFocusedTasksTopActivities();
             }
@@ -3265,6 +3274,12 @@
     }
 
     void updateTouchExcludeRegion() {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+            return;
+        }
         final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
         if (focusedTask == null) {
             mTouchExcludeRegion.setEmpty();
@@ -3303,6 +3318,11 @@
     }
 
     private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+        }
         final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
 
         if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
@@ -5129,8 +5149,13 @@
 
     /** @return the orientation of the display when it's rotation is ROTATION_0. */
     int getNaturalOrientation() {
-        return mBaseDisplayWidth < mBaseDisplayHeight
-                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        final Configuration config = getConfiguration();
+        if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) {
+            return config.orientation;
+        }
+        final Rect frame = mDisplayPolicy.getDecorInsetsInfo(
+                ROTATION_0, mBaseDisplayWidth, mBaseDisplayHeight).mConfigFrame;
+        return frame.width() <= frame.height() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
     }
 
     void performLayout(boolean initial, boolean updateInputWindows) {
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 23c135a..03574029 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -26,6 +26,7 @@
 import android.view.Display.Mode;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceControl.RefreshRateRange;
 
 import java.util.HashMap;
@@ -191,26 +192,35 @@
     public static class FrameRateVote {
         float mRefreshRate;
         @Surface.FrameRateCompatibility int mCompatibility;
+        @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy;
 
-        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            update(refreshRate, compatibility);
+
+
+        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                      @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            update(refreshRate, compatibility, selectionStrategy);
         }
 
         FrameRateVote() {
             reset();
         }
 
-        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                       @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            if (!refreshRateEquals(refreshRate)
+                    || mCompatibility != compatibility
+                    || mSelectionStrategy != selectionStrategy) {
                 mRefreshRate = refreshRate;
                 mCompatibility = compatibility;
+                mSelectionStrategy = selectionStrategy;
                 return true;
             }
             return false;
         }
 
         boolean reset() {
-            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
         }
 
         @Override
@@ -221,17 +231,20 @@
 
             FrameRateVote other = (FrameRateVote) o;
             return refreshRateEquals(other.mRefreshRate)
-                    && mCompatibility == other.mCompatibility;
+                    && mCompatibility == other.mCompatibility
+                    && mSelectionStrategy == other.mSelectionStrategy;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mRefreshRate, mCompatibility);
+            return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy);
+
         }
 
         @Override
         public String toString() {
-            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility
+                    + ", mSelectionStrategy=" + mSelectionStrategy;
         }
 
         private boolean refreshRateEquals(float refreshRate) {
@@ -265,7 +278,8 @@
                 for (Display.Mode mode : mDisplayInfo.supportedModes) {
                     if (preferredModeId == mode.getModeId()) {
                         return w.mFrameRateVote.update(mode.getRefreshRate(),
-                                Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                                Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
                     }
                 }
             }
@@ -273,7 +287,8 @@
 
         if (w.mAttrs.preferredRefreshRate > 0) {
             return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
-                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
         }
 
         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
@@ -282,7 +297,8 @@
             final String packageName = w.getOwningPackage();
             if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
                 return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
-                        Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                        Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                        SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 522e7d2..d5aa276 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1741,14 +1741,11 @@
      * @param starting                  The currently starting activity or {@code null} if there is
      *                                  none.
      * @param displayId                 The id of the display where operation is executed.
-     * @param markFrozenIfConfigChanged Whether to set {@link ActivityRecord#frozenBeforeDestroy} to
-     *                                  {@code true} if config changed.
      * @param deferResume               Whether to defer resume while updating config.
      * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
      * because of configuration update.
      */
-    boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,
-            boolean markFrozenIfConfigChanged, boolean deferResume) {
+    boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
         // First ensure visibility without updating the config just yet. We need this to know what
         // activities are affecting configuration now.
         // Passing null here for 'starting' param value, so that visibility of actual starting
@@ -1774,9 +1771,6 @@
         if (starting != null) {
             starting.reportDescendantOrientationChangeIfNeeded();
         }
-        if (starting != null && markFrozenIfConfigChanged && config != null) {
-            starting.frozenBeforeDestroy = true;
-        }
 
         if (displayContent != null) {
             // Update the configuration of the activities on the display.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5f08212..671acfc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5815,8 +5815,7 @@
             }
 
             mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
-                    mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */,
-                    false /* deferResume */);
+                    mDisplayContent.mDisplayId, false /* deferResume */);
         } finally {
             if (mTransitionController.isShellTransitionsEnabled()) {
                 mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 39b4480..fc92755 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1485,7 +1485,7 @@
                 // TODO: Remove this once visibilities are set correctly immediately when
                 // starting an activity.
                 notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
-                        true /* markFrozenIfConfigChanged */, false /* deferResume */);
+                        false /* deferResume */);
             }
 
             if (notUpdated) {
@@ -1856,7 +1856,7 @@
             // In that case go ahead and remove the freeze this activity has on the screen
             // since it is no longer visible.
             if (prev != null) {
-                prev.stopFreezingScreenLocked(true /*force*/);
+                prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
             }
             mPausingActivity = null;
         }
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 7d22b74..ac244c7 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -45,6 +45,10 @@
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
+        // TODO(b/315321016): Remove this class when the flag rollout is complete.
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
+        }
         mService = service;
         mDisplayContent = displayContent;
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 76c4a0e..f020bfa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2868,8 +2868,7 @@
             final WindowContainer<?> wc = mParticipants.valueAt(i);
             final DisplayContent dc = wc.asDisplayContent();
             if (dc == null || !mChanges.get(dc).hasChanged()) continue;
-            final int originalSeq = dc.getConfiguration().seq;
-            dc.sendNewConfiguration();
+            final boolean changed = dc.sendNewConfiguration();
             // Set to ready if no other change controls the ready state. But if there is, such as
             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
             // resumed activity. Then do not set to ready because the transition only contains
@@ -2877,7 +2876,7 @@
             if (!mReadyTrackerOld.mUsed) {
                 setReady(dc, true);
             }
-            if (originalSeq == dc.getConfiguration().seq) continue;
+            if (!changed) continue;
             // If the update is deferred, sendNewConfiguration won't deliver new configuration to
             // clients, then it is the caller's responsibility to deliver the changes.
             if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2b8e541..10dd334 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7308,7 +7308,12 @@
         }
     }
 
-    MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+    // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
+    // TODO(b/293587049): Remove after the refactoring is fully rolled out.
+    @Nullable
+    final MousePositionTracker mMousePositionTracker =
+            com.android.input.flags.Flags.enablePointerChoreographer() ? null
+                    : new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
         private boolean mLatestEventWasMouse;
@@ -7360,6 +7365,9 @@
     };
 
     void updatePointerIcon(IWindow client) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         int pointerDisplayId;
         float mouseX, mouseY;
 
@@ -7406,6 +7414,9 @@
     }
 
     void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         // Mouse position tracker has not been getting updates while dragging, update it now.
         if (!mMousePositionTracker.updatePosition(
                 displayContent.getDisplayId(), latestX, latestY)) {
@@ -7429,6 +7440,9 @@
         }
     }
     void setMousePointerDisplayId(int displayId) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         mMousePositionTracker.setPointerDisplayId(displayId);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a872fd0..4b99432 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -63,6 +63,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
@@ -1178,6 +1179,14 @@
                 mService.mRootWindowContainer.moveActivityToPinnedRootTask(
                         pipActivity, null /* launchIntoPipHostActivity */,
                         "moveActivityToPinnedRootTask", null /* transition */, entryBounds);
+
+                // Continue the pausing process after potential task reparenting.
+                if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) {
+                    pipActivity.getTask().schedulePauseActivity(
+                            pipActivity, false /* userLeaving */,
+                            false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+                }
+
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2b18f07..5721750 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -66,6 +66,7 @@
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Build;
+import android.os.DeadObjectException;
 import android.os.FactoryTest;
 import android.os.LocaleList;
 import android.os.Message;
@@ -915,7 +916,7 @@
             int i = mActivities.size();
             while (i > 0) {
                 i--;
-                mActivities.get(i).stopFreezingScreenLocked(true);
+                mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
             }
         }
     }
@@ -1675,6 +1676,10 @@
             @NonNull ClientTransactionItem transactionItem) {
         try {
             mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+        } catch (DeadObjectException e) {
+            // Expected if the process has been killed.
+            Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
+                    + transactionItem + " owner=" + mOwner);
         } catch (Exception e) {
             Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem="
                     + transactionItem + " owner=" + mOwner, e);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9c4ad5c..4e9d23c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -178,6 +178,7 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
@@ -5207,9 +5208,13 @@
 
         boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
         if (voteChanged) {
-            getPendingTransaction().setFrameRate(
-                    mSurfaceControl, mFrameRateVote.mRefreshRate,
-                    mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            getPendingTransaction()
+                    .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
+                        mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            if (explicitRefreshRateHints()) {
+                getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
+                        mFrameRateVote.mSelectionStrategy);
+            }
 
         }
     }
diff --git a/services/core/jni/com_android_server_app_GameManagerService.cpp b/services/core/jni/com_android_server_app_GameManagerService.cpp
index 3028813..f593f40 100644
--- a/services/core/jni/com_android_server_app_GameManagerService.cpp
+++ b/services/core/jni/com_android_server_app_GameManagerService.cpp
@@ -25,15 +25,21 @@
 
 namespace android {
 
-static void android_server_app_GameManagerService_nativeSetOverrideFrameRate(JNIEnv* env,
-                                                                             jclass clazz, jint uid,
-                                                                             jfloat frameRate) {
-    SurfaceComposerClient::setOverrideFrameRate(uid, frameRate);
+static void android_server_app_GameManagerService_nativeSetGameModeFrameRateOverride(
+        JNIEnv* env, jclass clazz, jint uid, jfloat frameRate) {
+    SurfaceComposerClient::setGameModeFrameRateOverride(uid, frameRate);
+}
+
+static void android_server_app_GameManagerService_nativeSetGameDefaultFrameRateOverride(
+        JNIEnv* env, jclass clazz, jint uid, jfloat frameRate) {
+    SurfaceComposerClient::setGameDefaultFrameRateOverride(uid, frameRate);
 }
 
 static const JNINativeMethod gMethods[] = {
-        {"nativeSetOverrideFrameRate", "(IF)V",
-         (void*)android_server_app_GameManagerService_nativeSetOverrideFrameRate},
+        {"nativeSetGameModeFrameRateOverride", "(IF)V",
+         (void*)android_server_app_GameManagerService_nativeSetGameModeFrameRateOverride},
+        {"nativeSetGameDefaultFrameRateOverride", "(IF)V",
+         (void*)android_server_app_GameManagerService_nativeSetGameDefaultFrameRateOverride},
 };
 
 int register_android_server_app_GameManagerService(JNIEnv* env) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 6f65965..bc05e77 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -88,6 +88,7 @@
 namespace android {
 
 static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
 
 // The exponent used to calculate the pointer speed scaling factor.
 // The scaling factor is calculated as 2 ^ (speed * exponent),
@@ -2737,6 +2738,15 @@
     im->setStylusPointerIconEnabled(enabled);
 }
 
+static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativeImplObj,
+                                                      jint thresholdTimeMs) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    if (ENABLE_INPUT_FILTER_RUST) {
+        im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold(
+                static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2836,6 +2846,8 @@
          (void*)nativeSetStylusButtonMotionEventsEnabled},
         {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
         {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
+        {"setAccessibilityBounceKeysThreshold", "(I)V",
+         (void*)nativeSetAccessibilityBounceKeysThreshold},
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 47c2a1b..29e258c 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -5,4 +5,12 @@
     namespace: "display_manager"
     description: "Feature flag for dual display blocking"
     bug: "278667199"
+}
+
+flag {
+    name: "enable_foldables_posture_based_closed_state"
+    namespace: "windowing_frontend"
+    description: "Enables smarter closed device state state for foldable devices"
+    bug: "309792734"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 44609ac..a0fb013 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -16,9 +16,7 @@
 
 package com.android.server.permission.access.permission
 
-import android.Manifest
 import android.permission.PermissionManager
-import android.permission.flags.Flags
 import android.util.Slog
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
@@ -61,7 +59,7 @@
         }
     }
 
-    fun MutateStateScope.removeInactiveDevicesPermission(activePersistentDeviceIds: Set<String>) {
+    fun MutateStateScope.trimDevicePermissionStates(deviceIds: Set<String>) {
         newState.userStates.forEachIndexed { _, userId, userState ->
             userState.appIdDevicePermissionFlags.forEachReversedIndexed { _, appId, _ ->
                 val appIdDevicePermissionFlags =
@@ -69,14 +67,11 @@
                 val devicePermissionFlags =
                     appIdDevicePermissionFlags.mutate(appId) ?: return@forEachReversedIndexed
 
-                val removePersistentDeviceIds = mutableSetOf<String>()
-                devicePermissionFlags.forEachIndexed { _, deviceId, _ ->
-                    if (!activePersistentDeviceIds.contains(deviceId)) {
-                        removePersistentDeviceIds.add(deviceId)
+                devicePermissionFlags.forEachReversedIndexed { _, deviceId, _ ->
+                    if (deviceId !in deviceIds) {
+                        devicePermissionFlags -= deviceId
                     }
                 }
-
-                removePersistentDeviceIds.forEach { deviceId -> devicePermissionFlags -= deviceId }
             }
         }
     }
@@ -122,6 +117,10 @@
         resetRuntimePermissions(packageName, userId)
     }
 
+    /**
+     * Reset permission states for all permissions requested by the given package, if no other
+     * package (sharing the App ID) request these permissions.
+     */
     fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) {
         // It's okay to skip resetting permissions for packages that are removed,
         // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
@@ -144,6 +143,7 @@
         }
     }
 
+    // Trims permission state for permissions not requested by the App ID anymore.
     private fun MutateStateScope.trimPermissionStates(appId: Int) {
         val requestedPermissions = MutableIndexedSet<String>()
         forEachPackageInAppId(appId) {
@@ -245,10 +245,6 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
-        if (!isDeviceAwarePermission(permissionName)) {
-            Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
-            return false
-        }
         val oldFlags =
             newState.userStates[userId]!!
                 .appIdDevicePermissionFlags[appId]
@@ -295,20 +291,8 @@
         synchronized(listenersLock) { listeners = listeners + listener }
     }
 
-    private fun isDeviceAwarePermission(permissionName: String): Boolean =
-        DEVICE_AWARE_PERMISSIONS.contains(permissionName)
-
     companion object {
         private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
-
-        /** These permissions are supported for virtual devices. */
-        // TODO: b/298661870 - Use new API to get the list of device aware permissions.
-        val DEVICE_AWARE_PERMISSIONS =
-            if (Flags.deviceAwarePermissionApis()) {
-                setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
-            } else {
-                emptySet<String>()
-            }
     }
 
     /** Listener for permission flags changes. */
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index a7d3249..f469ab5 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1551,10 +1551,11 @@
         permissionName: String,
         deviceId: Int,
     ): Int {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
-            if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
+            if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
                 Slog.i(
                     LOG_TAG,
                     "$permissionName is not device aware permission, " +
@@ -1586,10 +1587,11 @@
         deviceId: Int,
         flags: Int
     ): Boolean {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
-            if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
+            if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
                 Slog.i(
                     LOG_TAG,
                     "$permissionName is not device aware permission, " +
@@ -2312,20 +2314,19 @@
 
     override fun onSystemReady() {
         service.onSystemReady()
+
         virtualDeviceManagerInternal =
             LocalServices.getService(VirtualDeviceManagerInternal::class.java)
-
         virtualDeviceManagerInternal?.allPersistentDeviceIds?.let { persistentDeviceIds ->
             service.mutateState {
-                with(devicePolicy) { removeInactiveDevicesPermission(persistentDeviceIds) }
+                with(devicePolicy) { trimDevicePermissionStates(persistentDeviceIds) }
             }
         }
-
-        // trim permission states for the external devices, when they are removed.
         virtualDeviceManagerInternal?.registerPersistentDeviceIdRemovedListener { persistentDeviceId
             ->
             service.mutateState { with(devicePolicy) { onDeviceIdRemoved(persistentDeviceId) } }
         }
+
         permissionControllerManager =
             PermissionControllerManager(context, PermissionThread.getHandler())
     }
@@ -2860,5 +2861,14 @@
             PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
                 PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
                 PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+
+        /** These permissions are supported for virtual devices. */
+        // TODO: b/298661870 - Use new API to get the list of device aware permissions.
+        val DEVICE_AWARE_PERMISSIONS =
+            if (Flags.deviceAwarePermissionApisEnabled()) {
+                setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+            } else {
+                emptySet<String>()
+            }
     }
 }
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
index 832136c..925dad8 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt
@@ -38,7 +38,7 @@
  * AppIdPermissionPolicyPermissionStatesTest because these concepts don't apply to onUserAdded().
  */
 @RunWith(Parameterized::class)
-class AppIdPermissionPolicyPermissionDefinitionsTest : BaseAppIdPermissionPolicyTest() {
+class AppIdPermissionPolicyPermissionDefinitionsTest : BasePermissionPolicyTest() {
     @Parameterized.Parameter(0) lateinit var action: Action
 
     @Test
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 0547719..1237095 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -29,7 +29,7 @@
  * and resetRuntimePermissions() in AppIdPermissionPolicy
  */
 @RunWith(Parameterized::class)
-class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() {
+class AppIdPermissionPolicyPermissionResetTest : BasePermissionPolicyTest() {
     @Parameterized.Parameter(0) lateinit var action: Action
 
     @Test
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
index c44b2c5..6b9c9c2 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
@@ -39,7 +39,7 @@
  * states for onUserAdded(), onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy
  */
 @RunWith(Parameterized::class)
-class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest() {
+class AppIdPermissionPolicyPermissionStatesTest : BasePermissionPolicyTest() {
     @Parameterized.Parameter(0) lateinit var action: Action
 
     @Before
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
index e4e3368..cde46ab 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -30,7 +30,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
-class AppIdPermissionPolicyTest : BaseAppIdPermissionPolicyTest() {
+class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
     @Test
     fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
         val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BasePermissionPolicyTest.kt
similarity index 99%
rename from services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
rename to services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BasePermissionPolicyTest.kt
index 316f338..7b3f216 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BasePermissionPolicyTest.kt
@@ -53,7 +53,7 @@
  * Mocking unit test for AppIdPermissionPolicy.
  */
 @RunWith(AndroidJUnit4::class)
-abstract class BaseAppIdPermissionPolicyTest {
+abstract class BasePermissionPolicyTest {
     protected lateinit var oldState: MutableAccessState
     protected lateinit var newState: MutableAccessState
 
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt
new file mode 100644
index 0000000..996dd9d
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.test
+
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableDevicePermissionFlags
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.testutils.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/**
+ * This class tests permissions for external devices, we have separate policy to
+ * manage external device permissions.
+ */
+class DevicePermissionPolicyTest : BasePermissionPolicyTest() {
+    private val devicePermissionPolicy = DevicePermissionPolicy()
+
+    @Test
+    fun testOnAppIdRemoved_clearPermissionFlags() {
+        val packageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(packageState)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                onAppIdRemoved(APP_ID_1)
+            }
+        }
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(0)
+    }
+
+    @Test
+    fun testOnDeviceIdRemoved_clearPermissionFlags() {
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(requestingPackageState)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                onDeviceIdRemoved(DEVICE_ID_1)
+            }
+        }
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(0)
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+    }
+
+    @Test
+    fun testRemoveInactiveDevicesPermission_clearPermissionFlags() {
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(requestingPackageState)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                trimDevicePermissionStates(setOf(DEVICE_ID_2))
+            }
+        }
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(0)
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+    }
+
+    @Test
+    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
+        val mockListener =
+                mock<DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener> {}
+        devicePermissionPolicy.addOnPermissionFlagsChangedListener(mockListener)
+
+        GetStateScope(oldState).apply {
+            with(devicePermissionPolicy) {
+                onStateMutated()
+            }
+        }
+
+        verify(mockListener, times(1)).onStateMutated()
+    }
+
+    @Test
+    fun testOnStorageVolumeMounted_trimsPermissionsNotRequestAnymore() {
+        val packageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1, PERMISSION_NAME_0)
+            )
+        )
+        addPackageState(packageState)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED
+        )
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        val installedPackageState = mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1))
+        )
+        addPackageState(installedPackageState)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                onStorageVolumeMounted(null, listOf(PACKAGE_NAME_1), false)
+            }
+        }
+
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1))
+            .isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0))
+            .isEqualTo(0)
+    }
+
+
+    @Test
+    fun testResetRuntimePermissions_trimsPermissionStates() {
+        val packageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1))
+        )
+        addPackageState(packageState)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0)
+            }
+        }
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1))
+                .isEqualTo(0)
+    }
+
+    @Test
+    fun testResetRuntimePermissions_keepsPermissionStates() {
+        val packageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(
+                PACKAGE_NAME_1,
+                requestedPermissions = setOf(PERMISSION_NAME_1, PERMISSION_NAME_0)
+            )
+        )
+        val packageState2 = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_2, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(packageState)
+        addPackageState(packageState2)
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED
+        )
+        setPermissionFlags(
+            APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED
+        )
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+        assertThat(
+            getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState)
+        ).isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+
+        mutateState {
+            with(devicePermissionPolicy) {
+                resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0)
+            }
+        }
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1))
+                .isEqualTo(0)
+        assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0))
+                .isEqualTo(PermissionFlags.RUNTIME_GRANTED)
+    }
+
+    private fun getPermissionFlags(
+        appId: Int,
+        deviceId: String,
+        userId: Int,
+        permissionName: String,
+        state: MutableAccessState = newState
+    ): Int = state.userStates[userId]
+                ?.appIdDevicePermissionFlags
+                ?.get(appId)
+                ?.get(deviceId)
+                ?.getWithDefault(permissionName, 0)
+                ?: 0
+
+
+   private fun setPermissionFlags(
+        appId: Int,
+        deviceId: String,
+        userId: Int,
+        permissionName: String,
+        newFlags: Int,
+        state: MutableAccessState = oldState
+    ) {
+       val appIdDevicePermissionFlags =
+           state.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
+       val devicePermissionFlags =
+           appIdDevicePermissionFlags.mutateOrPut(appId) { MutableDevicePermissionFlags() }
+       val permissionFlags =
+           devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() }
+       permissionFlags.putWithDefault(permissionName, newFlags, 0)
+       if (permissionFlags.isEmpty()) {
+           devicePermissionFlags -= deviceId
+           if (devicePermissionFlags.isEmpty()) {
+               appIdDevicePermissionFlags -= appId
+           }
+       }
+   }
+
+    companion object {
+        private const val DEVICE_ID_1 = "cdm:1"
+        private const val DEVICE_ID_2 = "cdm:2"
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 820e44f..0fd424b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -334,6 +334,7 @@
         final boolean dead = (behavior == ProcessBehavior.DEAD);
 
         final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
+        r.mState = spy(r.mState);
         r.setPid(mNextPid.getAndIncrement());
         mActiveProcesses.add(r);
 
@@ -788,8 +789,8 @@
         }) {
             // Confirm expected OOM adjustments; we were invoked once to upgrade
             // and once to downgrade
-            assertEquals(String.valueOf(receiverApp), ActivityManager.PROCESS_STATE_RECEIVER,
-                    receiverApp.mState.getReportedProcState());
+            verify(receiverApp.mState, times(1).description(String.valueOf(receiverApp)))
+                    .setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
             verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
 
             if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 6906dec..9739e4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -18,7 +18,10 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
+import static com.android.server.app.GameManagerService.Injector;
 import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
+import static com.android.server.app.GameManagerService.PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED;
+import static com.android.server.app.GameManagerService.PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE;
 import static com.android.server.app.GameManagerService.SET_GAME_STATE;
 import static com.android.server.app.GameManagerService.WRITE_DELAY_MILLIS;
 import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE;
@@ -33,6 +36,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.any;
@@ -72,9 +76,12 @@
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.test.FakePermissionEnforcer;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
+import android.server.app.Flags;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -85,6 +92,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -108,6 +116,7 @@
 @Presubmit
 public class GameManagerServiceTests {
     @Mock MockContext mMockContext;
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final String TAG = "GameManagerServiceTests";
     private static final String PACKAGE_NAME_INVALID = "com.android.app";
     private static final int USER_ID_1 = 1001;
@@ -126,6 +135,11 @@
     private UserManager mMockUserManager;
     private BroadcastReceiver mShutDownActionReceiver;
 
+    private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
+
+    @Mock
+    private GameManagerServiceSystemPropertiesWrapper mSysPropsMock;
+
     @Captor
     ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
 
@@ -193,6 +207,8 @@
             switch (name) {
                 case Context.USER_SERVICE:
                     return mMockUserManager;
+                case Context.PERMISSION_ENFORCER_SERVICE:
+                    return mFakePermissionEnforcer;
             }
             throw new UnsupportedOperationException("Couldn't find system service: " + name);
         }
@@ -222,6 +238,8 @@
         when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
                 DEFAULT_PACKAGE_UID);
         LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
     }
 
     private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
@@ -1695,9 +1713,8 @@
         mockModifyGameModeGranted();
         final Context context = InstrumentationRegistry.getContext();
         GameManagerService gameManagerService =
-                new GameManagerService(mMockContext,
-                        mTestLooper.getLooper(),
-                        context.getFilesDir());
+                new GameManagerService(mMockContext, mTestLooper.getLooper(), context.getFilesDir(),
+                        new Injector());
         startUser(gameManagerService, USER_ID_1);
         startUser(gameManagerService, USER_ID_2);
 
@@ -1786,7 +1803,7 @@
         mockDeviceConfigBattery();
         final Context context = InstrumentationRegistry.getContext();
         GameManagerService gameManagerService = new GameManagerService(mMockContext,
-                mTestLooper.getLooper(), context.getFilesDir());
+                mTestLooper.getLooper(), context.getFilesDir(), new Injector());
         startUser(gameManagerService, USER_ID_1);
         startUser(gameManagerService, USER_ID_2);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
@@ -1962,7 +1979,7 @@
         assertTrue(
                 gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                         USER_ID_1));
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(60.0f));
         checkFps(gameManagerService, GameManager.GAME_MODE_CUSTOM, 60);
@@ -2035,7 +2052,7 @@
                 mTestLooper.getLooper()));
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(90.0f));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
@@ -2044,7 +2061,7 @@
         when(DeviceConfig.getProperty(anyString(), anyString()))
                 .thenReturn(configStringAfter);
         gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(0.0f));
     }
@@ -2061,14 +2078,14 @@
                 mTestLooper.getLooper()));
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(90.0f));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
 
         mockInterventionsDisabledNoOptInFromXml();
         gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(0.0f));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
@@ -2087,14 +2104,14 @@
         startUser(gameManagerService, USER_ID_1);
 
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(90.0f));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
 
         mockInterventionsEnabledAllOptInFromXml();
         gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
-        Mockito.verify(gameManagerService).setOverrideFrameRate(
+        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(0.0f));
     }
@@ -2390,4 +2407,140 @@
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
     }
+
+    @Test
+    public void testGameDefaultFrameRate_FlagOn() throws Exception {
+        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE);
+
+        GameManagerService gameManagerService = Mockito.spy(
+                new GameManagerService(mMockContext, mTestLooper.getLooper(),
+                        InstrumentationRegistry.getContext().getFilesDir(),
+                        new Injector(){
+                            @Override
+                            public GameManagerServiceSystemPropertiesWrapper
+                                    createSystemPropertiesWrapper() {
+                                return mSysPropsMock;
+                            }
+                        }));
+
+        when(mSysPropsMock.getInt(
+                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
+                anyInt())).thenReturn(60);
+        when(mSysPropsMock.getBoolean(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq(true))).thenReturn(true);
+        gameManagerService.onBootCompleted();
+
+        // Set up a game in the foreground.
+        String[] packages = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+        // Toggle game default frame rate on.
+        gameManagerService.toggleGameDefaultFrameRate(true);
+
+        // Verify that:
+        // 1) The system property is set correctly
+        // 2) setDefaultFrameRateOverride is called with correct arguments
+        Mockito.verify(mSysPropsMock).set(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq("true"));
+        Mockito.verify(gameManagerService, times(1))
+                .setGameDefaultFrameRateOverride(ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
+                                                 ArgumentMatchers.eq(60.0f));
+
+        // Adding another game to the foreground.
+        String anotherGamePkg = "another.game";
+        String[] packages2 = {anotherGamePkg};
+        mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
+        int somePackageId = DEFAULT_PACKAGE_UID + 1;
+        when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+        // Toggle game default frame rate off.
+        when(mSysPropsMock.getBoolean(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq(true))).thenReturn(false);
+        gameManagerService.toggleGameDefaultFrameRate(false);
+
+        // Verify that:
+        // 1) The system property is set correctly
+        // 2) setDefaultFrameRateOverride is called with correct arguments
+        Mockito.verify(mSysPropsMock).set(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq("false"));
+        Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride(
+                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f));
+        Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride(
+                ArgumentMatchers.eq(somePackageId), ArgumentMatchers.eq(0.0f));
+    }
+
+    @Test
+    public void testGameDefaultFrameRate_FlagOff() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
+        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE);
+
+        GameManagerService gameManagerService = Mockito.spy(
+                new GameManagerService(mMockContext, mTestLooper.getLooper(),
+                        InstrumentationRegistry.getContext().getFilesDir(),
+                        new Injector(){
+                            @Override
+                            public GameManagerServiceSystemPropertiesWrapper
+                                    createSystemPropertiesWrapper() {
+                                return mSysPropsMock;
+                            }
+                        }));
+
+        // Set up a game in the foreground.
+        String[] packages = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+        // Toggle game default frame rate on.
+        when(mSysPropsMock.getInt(
+                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
+                anyInt())).thenReturn(60);
+        when(mSysPropsMock.getBoolean(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq(true))).thenReturn(true);
+
+        gameManagerService.toggleGameDefaultFrameRate(true);
+
+        // Verify that:
+        // 1) System property is never set
+        // 2) setGameDefaultFrameRateOverride() should never be called if the flag is disabled.
+        Mockito.verify(mSysPropsMock, never()).set(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                anyString());
+        Mockito.verify(gameManagerService, never())
+                .setGameDefaultFrameRateOverride(anyInt(), anyFloat());
+
+        // Toggle game default frame rate off.
+        String anotherGamePkg = "another.game";
+        String[] packages2 = {anotherGamePkg};
+        mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
+        int somePackageId = DEFAULT_PACKAGE_UID + 1;
+        when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        when(mSysPropsMock.getBoolean(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq(true))).thenReturn(false);
+
+        gameManagerService.toggleGameDefaultFrameRate(false);
+        // Verify that:
+        // 1) System property is never set
+        // 2) setGameDefaultFrameRateOverride() should never be called if the flag is disabled.
+        Mockito.verify(mSysPropsMock, never()).set(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                anyString());
+        Mockito.verify(gameManagerService, never())
+                .setGameDefaultFrameRateOverride(anyInt(), anyFloat());
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index a250ac7..0702764 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -368,10 +368,15 @@
         }
     }
 
+    private JobInfo.Builder createJobInfoBuilder(int jobId) {
+        return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService"));
+    }
+
     private JobStatus createJobStatus(String testTag, int jobId) {
-        JobInfo jobInfo = new JobInfo.Builder(jobId,
-                new ComponentName(mContext, "TestQuotaJobService"))
-                .build();
+        return createJobStatus(testTag, createJobInfoBuilder(jobId).build());
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
     }
 
@@ -1333,39 +1338,70 @@
         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
                         3 * MINUTE_IN_MILLIS, 5), false);
+        final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
+        //noinspection deprecation
+        JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(1)
+                        .setImportantWhileForeground(true)
+                        .setPriority(JobInfo.PRIORITY_DEFAULT)
+                        .build());
+        JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build());
         setStandbyBucket(RARE_INDEX, job);
+        setStandbyBucket(RARE_INDEX, jobDefIWF);
+        setStandbyBucket(RARE_INDEX, jobHigh);
 
         setCharging();
         synchronized (mQuotaController.mLock) {
             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         setDischarging();
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            trackJobs(job, jobDefIWF, jobHigh);
             mQuotaController.prepareForExecutionLocked(job);
+            mQuotaController.prepareForExecutionLocked(jobDefIWF);
+            mQuotaController.prepareForExecutionLocked(jobHigh);
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
         synchronized (mQuotaController.mLock) {
-            assertEquals(7 * MINUTE_IN_MILLIS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
         }
     }
 
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f02e5a5..05e0e8f 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -3,6 +3,20 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "power_stats_ravenwood_tests",
+    srcs: [
+        "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java",
+        "src/com/android/server/power/stats/AggregatedPowerStatsTest.java",
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
+        "src/com/android/server/power/stats/PowerStatsAggregatorTest.java",
+        "src/com/android/server/power/stats/PowerStatsCollectorTest.java",
+        "src/com/android/server/power/stats/PowerStatsSchedulerTest.java",
+        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        "src/com/android/server/power/stats/PowerStatsUidResolverTest.java",
+    ],
+}
+
 android_test {
     name: "PowerStatsTests",
 
@@ -12,7 +26,7 @@
     ],
 
     exclude_srcs: [
-        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        ":power_stats_ravenwood_tests",
     ],
 
     static_libs: [
@@ -62,13 +76,14 @@
     static_libs: [
         "services.core",
         "modules-utils-binary-xml",
-
         "androidx.annotation_annotation",
         "androidx.test.rules",
+        "truth",
+        "mockito_ravenwood",
     ],
     srcs: [
-        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        ":power_stats_ravenwood_tests",
+        "src/com/android/server/power/stats/MockClock.java",
     ],
-    sdk_version: "test_current",
     auto_gen_config: true,
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
similarity index 98%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
index 48e2dd7..af83be0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
@@ -26,8 +26,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,7 +35,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class AggregatePowerStatsProcessorTest {
+public class AggregatedPowerStatsProcessorTest {
 
     @Test
     public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 8ca4ff6..993d834 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -38,7 +38,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index eb03a6c..1b045c5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -26,8 +26,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,7 +36,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MultiStateStatsTest {
-
     public static final int DIMENSION_COUNT = 2;
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 6704987..2456636 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,7 +24,6 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
-import android.text.format.DateFormat;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -39,7 +38,8 @@
 import org.junit.runner.RunWith;
 
 import java.text.ParseException;
-import java.util.Calendar;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 
@@ -60,7 +60,7 @@
     public void setup() throws ParseException {
         mHistory = new BatteryStatsHistory(32, 1024,
                 mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
-                mMonotonicClock);
+                mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class));
 
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(TEST_POWER_COMPONENT)
@@ -179,9 +179,9 @@
 
     @NonNull
     private static CharSequence formatDateTime(long timeInMillis) {
-        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
-        cal.setTimeInMillis(timeInMillis);
-        return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+        format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
+        return format.format(new Date(timeInMillis));
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 330f698..17a7d3e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -22,6 +22,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PersistableBundle;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,12 +30,18 @@
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PowerStatsCollectorTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
     private Handler mHandler;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
index 7257a94..beec661 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -24,26 +24,26 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.MonotonicClock;
-import com.android.internal.os.PowerProfile;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -51,39 +51,46 @@
 
 @RunWith(AndroidJUnit4.class)
 public class PowerStatsSchedulerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private PowerStatsStore mPowerStatsStore;
     private Handler mHandler;
     private MockClock mClock = new MockClock();
     private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
-    private MockBatteryStatsImpl mBatteryStats;
     private PowerStatsScheduler mPowerStatsScheduler;
-    private PowerProfile mPowerProfile;
     private PowerStatsAggregator mPowerStatsAggregator;
     private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private List<Long> mScheduledAlarms = new ArrayList<>();
+    private boolean mPowerStatsCollectionOccurred;
+
+    private static final int START_REALTIME = 7654321;
 
     @Before
     @SuppressWarnings("GuardedBy")
-    public void setup() {
-        final Context context = InstrumentationRegistry.getContext();
-
+    public void setup() throws IOException {
         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
 
         mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
-        mClock.realtime = 7654321;
+        mClock.realtime = START_REALTIME;
 
         HandlerThread bgThread = new HandlerThread("bg thread");
         bgThread.start();
-        File systemDir = context.getCacheDir();
         mHandler = new Handler(bgThread.getLooper());
         mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
-        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
-        mPowerProfile = mock(PowerProfile.class);
-        when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
-        mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+        mPowerStatsStore = new PowerStatsStore(
+                Files.createTempDirectory("PowerStatsSchedulerTest").toFile(),
+                mHandler, mAggregatedPowerStatsConfig);
         mPowerStatsAggregator = mock(PowerStatsAggregator.class);
-        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
-                TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
-                mMonotonicClock, mHandler, mBatteryStats);
+        mPowerStatsScheduler = new PowerStatsScheduler(
+                () -> mPowerStatsCollectionOccurred = true,
+                mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1),
+                mPowerStatsStore,
+                ((triggerAtMillis, tag, onAlarmListener, handler) ->
+                        mScheduledAlarms.add(triggerAtMillis)),
+                mClock, mMonotonicClock, () -> 12345L, mHandler);
     }
 
     @Test
@@ -113,7 +120,7 @@
             long endTimeWallClock =
                     mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
 
-            assertThat(startTime).isEqualTo(7654321 + 123);
+            assertThat(startTime).isEqualTo(START_REALTIME + 123);
             assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
             assertThat(Instant.ofEpochMilli(endTimeWallClock))
                     .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
@@ -142,11 +149,15 @@
         }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
                 any(Consumer.class));
 
-        mPowerStatsScheduler.schedulePowerStatsAggregation();
+        mPowerStatsScheduler.start(/*enabled*/ true);
         ConditionVariable done = new ConditionVariable();
         mHandler.post(done::open);
         done.block();
 
+        assertThat(mPowerStatsCollectionOccurred).isTrue();
+        assertThat(mScheduledAlarms).containsExactly(
+                START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1));
+
         verify(mPowerStatsAggregator, times(2))
                 .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
 
@@ -155,7 +166,7 @@
         // Skip the first entry, which was placed in the store at the beginning of this test
         PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
         PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
-        assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+        assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123);
         assertThat(timeFrame2.startMonotonicTime)
                 .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
         assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index a9967f6..71d64cf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -532,12 +532,11 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() {
+    public void testTwoFingerDoubleTap_StateIsIdle_shouldInActivated() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
         twoFingerTap();
-        twoFingerTap();
 
         assertIn(STATE_ACTIVATED);
         verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -546,13 +545,12 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
+    public void testTwoFingerDoubleTap_StateIsActivated_shouldInIdle() {
         goFromStateIdleTo(STATE_ACTIVATED);
         reset(mMockMagnificationLogger);
 
         twoFingerTap();
         twoFingerTap();
-        twoFingerTap();
 
         assertIn(STATE_IDLE);
         verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -561,11 +559,10 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
+    public void testTwoFingerDoubleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
-        twoFingerTap();
         twoFingerTapAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
@@ -575,11 +572,10 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
-    public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
+    public void testTwoFingerDoubleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
         goFromStateIdleTo(STATE_IDLE);
 
         twoFingerTap();
-        twoFingerTap();
         twoFingerSwipeAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index a7cf361..009bfb7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -52,8 +52,8 @@
 import android.test.mock.MockContentResolver;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -146,7 +146,7 @@
         assertTrue(mMagnificationConnectionManager.isConnected());
         verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
         verify(mMockConnection.getConnection()).setConnectionCallback(
-                any(IWindowMagnificationConnectionCallback.class));
+                any(IMagnificationConnectionCallback.class));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
index 8fdd884..07f3036 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
@@ -25,8 +25,8 @@
 import android.provider.Settings;
 import android.view.Display;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
@@ -49,7 +49,7 @@
     @Mock
     private AccessibilityTraceManager mTrace;
     @Mock
-    private IWindowMagnificationConnectionCallback mCallback;
+    private IMagnificationConnectionCallback mCallback;
     @Mock
     private MagnificationAnimationCallback mAnimationCallback;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
index 3d3d0b7..35b6c90 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
@@ -32,8 +32,8 @@
 import android.os.RemoteException;
 import android.view.Display;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IMagnificationConnectionCallback;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -53,7 +53,7 @@
     private boolean mHasPendingCallback = false;
     private boolean mWindowMagnificationEnabled = false;
     private IBinder.DeathRecipient mDeathRecipient;
-    private IWindowMagnificationConnectionCallback mIMirrorWindowCallback;
+    private IMagnificationConnectionCallback mIMagnificationCallback;
 
     private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500);
     private float mScale = 2.0f;
@@ -74,10 +74,10 @@
         mBinder = mock(Binder.class);
         when(mConnection.asBinder()).thenReturn(mBinder);
         doAnswer((invocation) -> {
-            mIMirrorWindowCallback = invocation.getArgument(0);
+            mIMagnificationCallback = invocation.getArgument(0);
             return null;
         }).when(mConnection).setConnectionCallback(
-                any(IWindowMagnificationConnectionCallback.class));
+                any(IMagnificationConnectionCallback.class));
 
         doAnswer((invocation) -> {
             mDeathRecipient = invocation.getArgument(0);
@@ -166,8 +166,8 @@
         return mDeathRecipient;
     }
 
-    IWindowMagnificationConnectionCallback getConnectionCallback() {
-        return mIMirrorWindowCallback;
+    IMagnificationConnectionCallback getConnectionCallback() {
+        return mIMagnificationCallback;
     }
 
     Rect getMirrorWindowFrame() {
@@ -185,10 +185,10 @@
         if (!mHasPendingCallback) {
             throw new IllegalStateException("There is no any pending callbacks");
         }
-        if (mWindowMagnificationEnabled && mIMirrorWindowCallback != null) {
-            mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY,
+        if (mWindowMagnificationEnabled && mIMagnificationCallback != null) {
+            mIMagnificationCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY,
                     mMirrorWindowFrame);
-            mIMirrorWindowCallback.onSourceBoundsChanged(TEST_DISPLAY,
+            mIMagnificationCallback.onSourceBoundsChanged(TEST_DISPLAY,
                     mSourceBounds);
         }
         sendAnimationEndCallbackIfNeeded(success);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index a3b67ae..c99e040 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -72,15 +72,15 @@
     public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4;
     public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5;
     public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6;
-    public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7;
-    public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8;
-    public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9;
+    public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP = 7;
+    public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 8;
+    public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 9;
     //TODO: Test it after can injecting Handler to GestureMatcher is available.
 
     public static final int FIRST_STATE = STATE_IDLE;
     public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD;
     public static final int LAST_STATE_WITH_MULTI_FINGER =
-            STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD;
+            STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD;
 
     // Co-prime x and y, to potentially catch x-y-swapped errors
     public static final float DEFAULT_TAP_X = 301;
@@ -257,15 +257,15 @@
             break;
             case STATE_SHOW_MAGNIFIER_SHORTCUT:
             case STATE_SHOW_MAGNIFIER_TRIPLE_TAP:
-            case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP:
+            case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP:
                 check(isWindowMagnifierEnabled(DISPLAY_0), state);
                 check(mWindowMagnificationGestureHandler.mCurrentState
                         == mWindowMagnificationGestureHandler.mDetectingState, state);
                 break;
             case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
             case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
-            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
+            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                 check(isWindowMagnifierEnabled(DISPLAY_0), state);
                 check(mWindowMagnificationGestureHandler.mCurrentState
                         == mWindowMagnificationGestureHandler.mViewportDraggingState, state);
@@ -337,8 +337,7 @@
                     tapAndHold();
                 }
                 break;
-                case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
-                    twoFingerTap();
+                case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
                     twoFingerTap();
                     twoFingerTap();
                     // Wait for two-finger tap gesture completed.
@@ -346,17 +345,15 @@
                     InstrumentationRegistry.getInstrumentation().waitForIdleSync();
                 }
                 break;
-                case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
-                    twoFingerTap();
+                case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                     twoFingerTap();
                     twoFingerTapAndHold();
                 }
                 break;
-                case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+                case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
                     // enabled then perform two finger triple tap and hold gesture
                     goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
                     twoFingerTap();
-                    twoFingerTap();
                     twoFingerTapAndHold();
                 }
                 break;
@@ -394,16 +391,15 @@
             }
             break;
             case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+            case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
                 send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
                 break;
             case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
-            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+            case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
                 send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
                 returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT);
                 break;
-            case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
-                twoFingerTap();
+            case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
                 twoFingerTap();
                 twoFingerTap();
                 // Wait for two-finger tap gesture completed.
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index cc3c880..f1c1dc3 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -266,18 +266,22 @@
                     .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
 
             // no metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_DEFAULT.getBytes()));
-            assertFalse(
-                    mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
-                            mFakeBtDevice.getAddress()));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes())) {
+                assertFalse(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+                        mFakeBtDevice.getAddress()));
+            }
 
             // metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_HEADSET.getBytes()));
-            assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
-                    mFakeBtDevice.getAddress()));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_HEADSET.getBytes())) {
+                assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+                        mFakeBtDevice.getAddress()));
+            }
         } finally {
+            // reset the metadata device type
+            mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes());
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
         }
@@ -304,25 +308,30 @@
                     .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
 
             // no metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_DEFAULT.getBytes()));
-            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
-                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
-                            mFakeBtDevice.getAddress()));
-            verify(mMockAudioService,
-                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
-                    eq(devState));
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes())) {
+                assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
+                        mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+                                mFakeBtDevice.getAddress()));
+                verify(mMockAudioService,
+                        timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
+                        eq(devState));
+            }
 
             // metadata set
-            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
-                    DEVICE_TYPE_HEADSET.getBytes()));
-            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
-                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
-                            mFakeBtDevice.getAddress()));
-            verify(mMockAudioService,
-                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
-                    any());
+            if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_HEADSET.getBytes())) {
+                assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
+                        mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+                                mFakeBtDevice.getAddress()));
+                verify(mMockAudioService,
+                        timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
+                        any());
+            }
         } finally {
+            // reset the metadata device type
+            mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+                    DEVICE_TYPE_DEFAULT.getBytes());
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                     .dropShellPermissionIdentity();
         }
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 330dbb8..861d14a 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -30,13 +30,19 @@
 import com.android.internal.annotations.GuardedBy;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
+@RunWith(Parameterized.class)
 public class AnrTimerTest {
 
     // The commonly used message timeout key.
@@ -106,6 +112,16 @@
     }
 
     /**
+     * Force AnrTimer to use the test parameter for the feature flag.
+     */
+    class TestInjector extends AnrTimer.Injector {
+        @Override
+        boolean anrTimerServiceEnabled() {
+            return mEnabled;
+        }
+    }
+
+    /**
      * An instrumented AnrTimer.
      */
     private static class TestAnrTimer extends AnrTimer<TestArg> {
@@ -137,6 +153,17 @@
         assertEquals(actual.what, MSG_TIMEOUT);
     }
 
+    @Parameters(name = "featureEnabled={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {false}, {true} });
+    }
+
+    /** True if the feature is enabled. */
+    private boolean mEnabled;
+
+    public AnrTimerTest(boolean featureEnabled) {
+        mEnabled = featureEnabled;
+    }
 
     /**
      * Verify that a simple expiration succeeds.  The timer is started for 10ms.  The test
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 8891413..6335b3b 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -40,6 +40,7 @@
         "servicestests-utils",
         "testables",
         "truth",
+        "TestParameterInjector",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
@@ -82,4 +83,7 @@
         "libutils",
         "netd_aidl_interface-V5-cpp",
     ],
+
+    // Required for TestParameterInjector
+    javacflags: ["-parameters"],
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 5febd02..260ee396 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,30 +16,51 @@
 
 package com.android.server.notification;
 
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.ColorDisplayManager;
 import android.os.PowerManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
 import android.testing.TestableContext;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import com.google.testing.junit.testparameterinjector.TestParameters;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
 public class DefaultDeviceEffectsApplierTest {
 
     @Rule
@@ -52,10 +73,31 @@
     @Mock UiModeManager mUiModeManager;
     @Mock WallpaperManager mWallpaperManager;
 
+    private enum ChangeOrigin {
+        ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
+        ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
+        ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
+        ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
+        ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
+        ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
+        ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
+
+        private final int mValue;
+
+        ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
+            mValue = value;
+        }
+
+        @ZenModeConfig.ConfigChangeOrigin
+        public int value() {
+            return mValue;
+        }
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = new TestableContext(InstrumentationRegistry.getContext(), null);
+        mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null));
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
         mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
@@ -74,25 +116,33 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects);
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
-        verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :)
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
     }
 
     @Test
-    public void apply_removesEffects() {
+    public void apply_removesPreviouslyAppliedEffects() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
+        ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .setShouldDimWallpaper(true)
+                .build();
+        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
-        mApplier.apply(noEffects);
+        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
-        verify(mColorDisplayManager).setSaturationLevel(eq(100));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
-        verifyZeroInteractions(mUiModeManager);
+        verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
     }
 
     @Test
@@ -107,9 +157,125 @@
                 .setShouldDisplayGrayscale(true)
                 .setShouldUseNightMode(true)
                 .build();
-        mApplier.apply(effects);
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         // (And no crash from missing services).
     }
+
+    @Test
+    public void apply_someEffects_onlyThoseEffectsApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .build();
+        mApplier.apply(effects, UPDATE_ORIGIN_USER);
+
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+        verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
+        verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void apply_onlyEffectDeltaApplied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
+                UPDATE_ORIGIN_USER);
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+        // Apply a second effect and remove the first one.
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(),
+                UPDATE_ORIGIN_USER);
+
+        // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
+        verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
+        verifyZeroInteractions(mPowerManager);
+        verifyZeroInteractions(mUiModeManager);
+    }
+
+    @Test
+    public void apply_nightModeFromApp_appliedOnScreenOff() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                UPDATE_ORIGIN_APP);
+
+        // Effect was not yet applied, but a broadcast receiver was registered.
+        verifyZeroInteractions(mUiModeManager);
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
+                intentFilterCaptor.capture(), anyInt());
+        assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF);
+        BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue();
+
+        // Now the "screen off" event comes.
+        screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+
+        // So the effect is applied, and we stopped listening for this event.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext).unregisterReceiver(eq(screenOffReceiver));
+    }
+
+    @Test
+    public void apply_nightModeWithScreenOff_appliedImmediately(
+            @TestParameter ChangeOrigin origin) {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        when(mPowerManager.isInteractive()).thenReturn(false);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                origin.value());
+
+        // Effect was applied, and no broadcast receiver was registered.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+    }
+
+    @Test
+    @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}",
+            "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}"})
+    public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                origin.value());
+
+        // Effect was applied, and no broadcast receiver was registered.
+        verify(mUiModeManager).setNightModeActivatedForCustomMode(
+                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+    }
+
+    @Test
+    @TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
+            "{origin: ORIGIN_UNKNOWN}"})
+    public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+                origin.value());
+
+        // Effect was not applied, will be on next screen-off.
+        verifyZeroInteractions(mUiModeManager);
+        verify(mContext).registerReceiver(any(),
+                argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))),
+                anyInt());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ee08fd2..36d55a4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -209,6 +209,7 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.permission.PermissionManager;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -220,6 +221,7 @@
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
@@ -970,6 +972,12 @@
         return new NotificationRecord(mContext, sbn, channel);
     }
 
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel,
+            long postTime) {
+        final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0);
+        return new NotificationRecord(mContext, sbn, channel);
+    }
+
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
         return generateNotificationRecord(channel, 1, userId);
     }
@@ -8929,8 +8937,8 @@
         mBinderService.addAutomaticZenRule(rule, "com.android.settings");
 
         // verify that zen mode helper gets passed in a package name of "android"
-        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -8951,8 +8959,8 @@
         mBinderService.addAutomaticZenRule(rule, "com.android.settings");
 
         // verify that zen mode helper gets passed in a package name of "android"
-        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
-                anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI));  // system call
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
     }
 
     @Test
@@ -8972,8 +8980,44 @@
 
         // verify that zen mode helper gets passed in the package name from the arg, not the owner
         verify(mockZenModeHelper).addAutomaticZenRule(
-                eq("another.package"), eq(rule), anyString(), anyInt(),
-                eq(ZenModeHelper.FROM_APP));  // doesn't count as a system/systemui call
+                eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP),
+                anyString(), anyInt());  // doesn't count as a system/systemui call
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception {
+        mService.setCallerIsNormalPackage();
+        mService.setZenHelper(mock(ZenModeHelper.class));
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+                .setType(AutomaticZenRule.TYPE_MANAGED)
+                .setOwner(new ComponentName("pkg", "cls"))
+                .build();
+        when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(true);
+
+        mBinderService.addAutomaticZenRule(rule, "pkg");
+        // No exception!
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void testAddAutomaticZenRule_typeManagedCannotBeUsedByRegularApps() throws Exception {
+        mService.setCallerIsNormalPackage();
+        mService.setZenHelper(mock(ZenModeHelper.class));
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+                .setType(AutomaticZenRule.TYPE_MANAGED)
+                .setOwner(new ComponentName("pkg", "cls"))
+                .build();
+        when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(false);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mBinderService.addAutomaticZenRule(rule, "pkg"));
     }
 
     @Test
@@ -13185,7 +13229,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13207,7 +13251,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13223,7 +13267,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
     }
 
     @Test
@@ -13271,7 +13315,8 @@
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
 
         verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq("package"), anyString(), anyInt(), anyBoolean());
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
+                anyInt());
     }
 
     @Test
@@ -13280,6 +13325,7 @@
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = zenModeHelper;
+        mService.setCallerIsNormalPackage();
         when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
                 .thenReturn(true);
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
@@ -13292,7 +13338,7 @@
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
 
         verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq("package"), anyString(), anyInt(), anyBoolean());
+                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
     }
 
     @Test
@@ -13309,6 +13355,175 @@
         assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
     }
 
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        // Create old notification.
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel specific notifications via listener.
+        String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is logged.
+        verify(mAppOpsManager).noteOpNoThrow(
+                AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null);
+    }
+
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notifications.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr1);
+
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel specific notifications via listener.
+        String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled()
+            throws RemoteException {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        // Create old notification.
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel specific notifications via listener.
+        String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is not logged due to flag being disabled.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        // Create old notification.
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel all notifications via listener.
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is logged.
+        verify(mAppOpsManager).noteOpNoThrow(
+                AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null);
+    }
+
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notifications.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr1);
+
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel all notifications via listener.
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled()
+            throws RemoteException {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        // Create old notification.
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(nr2);
+
+        // Cancel all notifications via listener.
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+
+        // Notifications should not be active anymore.
+        StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG);
+        assertThat(notifications).isEmpty();
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Ensure cancel event is not logged due to flag being disabled.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
     private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
             throws RemoteException {
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index fe21103..5ddac03 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -67,7 +67,6 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -2370,7 +2369,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // create notification channel that can bypass dnd
@@ -2380,18 +2379,18 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // delete channels
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2407,7 +2406,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // Recreate a channel & now the app has dnd access granted and can set the bypass dnd field
@@ -2417,7 +2416,7 @@
                 uid, false);
 
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2433,7 +2432,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // create notification channel that can bypass dnd, using local app level settings
@@ -2443,18 +2442,18 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // delete channels
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
         assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2481,8 +2480,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(),
-                anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2504,7 +2502,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2526,7 +2524,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2542,7 +2540,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
                 uid, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // update channel so it CAN bypass dnd:
@@ -2550,7 +2548,7 @@
         channel.setBypassDnd(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
         assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
 
         // update channel so it can't bypass dnd:
@@ -2558,7 +2556,7 @@
         channel.setBypassDnd(false);
         mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2571,7 +2569,7 @@
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper.syncChannelsBypassingDnd();
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
@@ -2581,7 +2579,7 @@
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
         resetZenModeHelper();
     }
 
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 ef6fced..646ee3f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -45,6 +45,12 @@
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
 
@@ -53,9 +59,6 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static com.android.server.notification.ZenModeHelper.FROM_APP;
-import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI;
-import static com.android.server.notification.ZenModeHelper.FROM_USER;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -305,8 +308,8 @@
         mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
         serializer.endDocument();
         serializer.flush();
-        mZenModeHelper.setConfig(new ZenModeConfig(), null, "writing xml", Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml",
+                Process.SYSTEM_UID);
         return baos;
     }
 
@@ -321,7 +324,8 @@
         serializer.flush();
         ZenModeConfig newConfig = new ZenModeConfig();
         newConfig.user = userId;
-        mZenModeHelper.setConfig(newConfig, null, "writing xml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml",
+                Process.SYSTEM_UID);
         return baos;
     }
 
@@ -838,7 +842,7 @@
         mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -865,7 +869,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -892,7 +896,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenModeLocked("test", true);
+            mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -905,8 +909,8 @@
         setupZenConfig();
 
         // Turn manual zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null,
-                "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                null, "test", CUSTOM_PKG_UID);
 
         // audio manager shouldn't do anything until the handler processes its messages
         verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
@@ -1185,7 +1189,8 @@
         List<StatsEvent> events = new LinkedList<>();
 
         mZenModeHelper.pullRules(events);
-        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         assertTrue(-1
                 == mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
     }
@@ -1240,12 +1245,14 @@
         config10.user = 10;
         config10.allowAlarms = true;
         config10.allowMedia = true;
-        mZenModeHelper.setConfig(config10, null, "writeXml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
+                Process.SYSTEM_UID);
         ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
         config11.user = 11;
         config11.allowAlarms = false;
         config11.allowMedia = false;
-        mZenModeHelper.setConfig(config11, null, "writeXml", Process.SYSTEM_UID, true);
+        mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
+                Process.SYSTEM_UID);
 
         // Backup user 10 and reset values.
         ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
@@ -1849,8 +1856,8 @@
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
             // We need the package name to be something that's not "android" so there aren't any
             // existing rules under that package.
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1860,8 +1867,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1881,8 +1888,8 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1892,8 +1899,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1913,8 +1920,8 @@
                     ZenModeConfig.toScheduleConditionId(si),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             assertNotNull(id);
         }
         try {
@@ -1924,8 +1931,8 @@
                     ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                     new ZenPolicy.Builder().build(),
                     NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
-                    CUSTOM_PKG_UID, FROM_APP);
+            String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+                    "test", CUSTOM_PKG_UID);
             fail("allowed too many rules to be created");
         } catch (IllegalArgumentException e) {
             // yay
@@ -1940,8 +1947,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1961,8 +1968,8 @@
                 new ComponentName("android", "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1985,11 +1992,12 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                CUSTOM_PKG_UID, false);
+                UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals(STATE_TRUE, ruleInConfig.condition.state);
@@ -2004,8 +2012,8 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
                 null,
@@ -2014,7 +2022,7 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
 
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertEquals("NEW", ruleInConfig.name);
@@ -2029,15 +2037,15 @@
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
 
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRule(id, "test", CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
 
@@ -2049,16 +2057,16 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 new ZenPolicy.Builder().build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
-                CUSTOM_PKG_UID, FROM_APP);
+        String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
 
         assertTrue(id != null);
         ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
         assertTrue(ruleInConfig != null);
         assertEquals(zenRule.getName(), ruleInConfig.name);
 
-        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), "test",
-                CUSTOM_PKG_UID, false);
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test",
+                CUSTOM_PKG_UID);
         assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
     }
 
@@ -2073,17 +2081,18 @@
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         Condition condition = new Condition(sharedUri, "", STATE_TRUE);
-        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2097,7 +2106,8 @@
         }
 
         condition = new Condition(sharedUri, "", STATE_FALSE);
-        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+        mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
             if (rule.id.equals(id)) {
@@ -2133,7 +2143,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_APP);
+                UPDATE_ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2167,7 +2177,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2195,7 +2205,8 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(zde)
                         .build(),
-                "reasons", 0, FROM_USER);
+                UPDATE_ORIGIN_USER,
+                "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2212,7 +2223,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2223,7 +2234,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(updateFromApp)
                         .build(),
-                "reasons", 0, FROM_APP);
+                UPDATE_ORIGIN_APP, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2244,7 +2255,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2254,7 +2265,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromSystem)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
@@ -2271,7 +2282,7 @@
                         .setOwner(OWNER)
                         .setDeviceEffects(original)
                         .build(),
-                "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
 
         ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
                 .setShouldUseNightMode(true) // Good
@@ -2281,7 +2292,7 @@
                 new AutomaticZenRule.Builder("Rule", CONDITION_ID)
                         .setDeviceEffects(updateFromUser)
                         .build(),
-                "reasons", 0, FROM_USER);
+                UPDATE_ORIGIN_USER, "reasons", 0);
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
@@ -2293,16 +2304,16 @@
         setupZenConfig();
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
         assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", null, Process.SYSTEM_UID);
 
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -2312,15 +2323,15 @@
         setupZenConfig();
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
 
         // and also that it works to turn it back off again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                null, Process.SYSTEM_UID);
 
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
@@ -2332,13 +2343,13 @@
 
         // Turn zen mode on (to important_interruptions)
         // Need to additionally call the looper in order to finish the post-apply-config process
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now turn zen mode off, but via a different package UID -- this should get registered as
         // "not an action by the user" because some other app is changing zen mode
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID,
-                false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null,
+                CUSTOM_PKG_UID);
 
         // In total, this should be 2 loggable changes
         assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -2397,17 +2408,18 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                Process.SYSTEM_UID);
 
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // Add a new system rule
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2417,15 +2429,16 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 3: turn on the system rule
         mZenModeHelper.setAutomaticZenRuleState(systemId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Event 4: "User" deletes the rule
-        mZenModeHelper.removeAutomaticZenRule(systemId, "", Process.SYSTEM_UID, true);
+        mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // In total, this represents 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2486,26 +2499,26 @@
         setupZenConfig();
 
         // First just turn zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now change the policy slightly; want to confirm that this'll be reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.allowAlarms = true;
         newConfig.allowRepeatCallers = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
         // is off.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                null, Process.SYSTEM_UID);
 
         // Change the policy again
         newConfig.allowMessages = false;
         newConfig.allowRepeatCallers = true;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Total events: we only expect ones for turning on, changing policy, and turning off
         assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -2548,8 +2561,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 2, same as rule 1
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2558,8 +2571,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 3, has stricter settings than the default settings
         ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -2572,28 +2585,28 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 ruleConfig.toZenPolicy(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // First: turn on rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Second: turn on rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Third: turn on rule 3
         mZenModeHelper.setAutomaticZenRuleState(id3,
                 new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // Fourth: Turn *off* rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
 
         // This should result in a total of four events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2649,7 +2662,7 @@
         // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
         // given that we don't have any zen rules active.
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.evaluateZenModeLocked("test", true);
+        mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
 
         // Check that the change actually took: zen mode should be off now
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
@@ -2672,8 +2685,8 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Rule 2, same as rule 1 but owned by the system
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2682,37 +2695,37 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
         // re-evaluated to the one associat.d with CUSTOM_PKG_NAME.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
         // (nor even looked up; the mock PackageManager won't handle "android" as input).
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Disable rule 1. Because this looks like a user action, the UID should not be modified
         // from the system-provided one.
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Process.SYSTEM_UID);
 
         // Add a manual rule. Any manual rule changes should not get calling uids reassigned.
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                CUSTOM_PKG_UID, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                "", null, CUSTOM_PKG_UID);
 
         // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
         // the system, we keep the UID info.
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                12345, false);
+                UPDATE_ORIGIN_APP, 12345);
 
         // That was 5 events total
         assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2767,26 +2780,26 @@
         // Turn on zen mode with a manual rule with an enabler set. This should *not* count
         // as a user action, and *should* get its UID reassigned.
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                CUSTOM_PKG_NAME, "", Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
 
         // Now change apps bypassing to true
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.areChannelsBypassingDnd = true;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // and then back to false, all without changing anything else
         newConfig.areChannelsBypassingDnd = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Turn off manual mode, call from a package: don't reset UID even though enabler is set
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null,
-                CUSTOM_PKG_NAME, "", 12345, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
+                CUSTOM_PKG_NAME, 12345);
 
         // And likewise when turning it back on again
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                CUSTOM_PKG_NAME, "", 12345, false);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+                "", CUSTOM_PKG_NAME, 12345);
 
         // These are 5 events in total.
         assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2831,15 +2844,15 @@
         setupZenConfig();
 
         // First just turn zen mode on
-        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
 
         // Now change only the channels part of the policy; want to confirm that this'll be
         // reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.allowPriorityChannels = false;
-        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
-                true);
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Total events: one for turning on, one for changing policy
         assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -2881,13 +2894,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // inspect the consolidated policy. Based on setupZenConfig() values.
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
@@ -2924,13 +2937,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
         // policy for every field specified, and take default values for unspecified things
@@ -2960,13 +2973,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // custom policy for rule 2
         ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -2985,12 +2998,12 @@
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // now both rules should be on, and the consolidated policy should reflect the most
         // restrictive option of each of the two
@@ -3022,13 +3035,13 @@
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 customPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
-                Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable the rule; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // confirm that channels make it through
         assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3045,12 +3058,12 @@
                 strictPolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // enable rule 2; this will update the consolidated policy
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // rule 2 should override rule 1
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3121,7 +3134,7 @@
 
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
 
-        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, true, FROM_APP);
+        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
 
         assertEquals(NAME, rule.name);
         assertEquals(OWNER, rule.component);
@@ -3149,7 +3162,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3165,8 +3178,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -3184,7 +3197,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3200,8 +3213,8 @@
         mZenModeHelper.addCallback(callback);
 
         zenRule.setEnabled(true);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -3220,7 +3233,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[1];
@@ -3237,7 +3250,7 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
@@ -3256,7 +3269,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -3274,10 +3287,10 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                null, "", Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3296,7 +3309,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         CountDownLatch latch = new CountDownLatch(1);
         final int[] actualStatus = new int[2];
@@ -3314,11 +3327,11 @@
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_FALSE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
         assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3336,21 +3349,21 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+                zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
         mZenModeHelper.setAutomaticZenRuleState(createdId,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                Process.SYSTEM_UID, true);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Event 2: Snooze rule by turning off DND
-        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
-                Process.SYSTEM_UID, true);
+        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", null, Process.SYSTEM_UID);
 
         // Event 3: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
-        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
-                FROM_SYSTEM_OR_SYSTEMUI);
+        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "", Process.SYSTEM_UID);
 
         assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
     }
@@ -3359,18 +3372,20 @@
     public void testDeviceEffects_applied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
                 .setShouldDimWallpaper(true)
                 .build();
         String ruleId = addRuleWithEffects(effects);
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verifyNoMoreInteractions(mDeviceEffectsApplier);
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(effects));
+        verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP));
     }
 
     @Test
@@ -3380,31 +3395,66 @@
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
-        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS));
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP));
     }
 
     @Test
+    public void testDeviceEffects_changeToConsolidatedEffects_applied() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+        String ruleId = addRuleWithEffects(
+                new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build());
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+        verify(mDeviceEffectsApplier).apply(
+                eq(new ZenDeviceEffects.Builder()
+                        .setShouldDisplayGrayscale(true)
+                        .build()),
+                eq(UPDATE_ORIGIN_APP));
+
+        // Now create and activate a second rule that adds more effects.
+        String secondRuleId = addRuleWithEffects(
+                new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build());
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(
+                eq(new ZenDeviceEffects.Builder()
+                        .setShouldDisplayGrayscale(true)
+                        .setShouldDimWallpaper(true)
+                        .build()),
+                eq(UPDATE_ORIGIN_APP));
+    }
+    @Test
     public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
 
         // Now create and activate a second rule that doesn't add any more effects.
         String secondRuleId = addRuleWithEffects(zde);
-        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID,
-                false);
+        mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
 
         verifyNoMoreInteractions(mDeviceEffectsApplier);
@@ -3416,21 +3466,50 @@
 
         ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
         String ruleId = addRuleWithEffects(zde);
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
-        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
         mTestableLooper.processAllMessages();
-        verify(mDeviceEffectsApplier, never()).apply(any());
+        verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
 
         mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
-        verify(mDeviceEffectsApplier).apply(eq(zde));
+        verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT));
+    }
+
+    @Test
+    public void testDeviceEffects_onUserSwitch_appliedImmediately() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+        // Initialize default configurations (default rules) for both users.
+        mZenModeHelper.onUserSwitched(1);
+        mZenModeHelper.onUserSwitched(2);
+
+        // Current user is now 2. Tweak a rule for user 1 so it's active and has effects.
+        ZenRule user1Rule = mZenModeHelper.mConfigs.get(1).automaticRules.valueAt(0);
+        user1Rule.enabled = true;
+        user1Rule.condition = new Condition(user1Rule.conditionId, "on", STATE_TRUE);
+        user1Rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldUseNightMode(true)
+                .build();
+        verifyNoMoreInteractions(mDeviceEffectsApplier);
+
+        mZenModeHelper.onUserSwitched(1);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects),
+                eq(UPDATE_ORIGIN_INIT_USER));
     }
 
     private String addRuleWithEffects(ZenDeviceEffects effects) {
         AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
                 .setDeviceEffects(effects)
                 .build();
-        return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP);
+        return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
+                CUSTOM_PKG_UID);
     }
 
     @Test
@@ -3455,7 +3534,7 @@
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -3505,7 +3584,7 @@
         assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
         assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
 
-        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
         assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
 
         mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 744cb63..8a79fe4 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -44,6 +44,7 @@
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth",
+        "frameworks-base-testutils",
     ],
 
     libs: [
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
new file mode 100644
index 0000000..656957c
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.voiceinteraction;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.PermissionEnforcer;
+import android.os.Process;
+import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SetSandboxedTrainingDataAllowedTest {
+
+    @Captor private ArgumentCaptor<Integer> mOpIdCaptor, mUidCaptor, mOpModeCaptor;
+
+    @Mock
+    private AppOpsManager mAppOpsManager;
+
+    @Mock
+    private VoiceInteractionManagerServiceImpl mVoiceInteractionManagerServiceImpl;
+
+    private FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer();
+
+    private Context mContext;
+
+    private VoiceInteractionManagerService mVoiceInteractionManagerService;
+    private VoiceInteractionManagerService.VoiceInteractionManagerServiceStub
+            mVoiceInteractionManagerServiceStub;
+
+    private ApplicationInfo mApplicationInfo = new ApplicationInfo();
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.WARN)
+                    .mockStatic(LocalServices.class)
+                    .mockStatic(PermissionEnforcer.class)
+                    .build();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+
+        doReturn(mPermissionEnforcer).when(() -> PermissionEnforcer.fromContext(any()));
+        doReturn(mock(PermissionManagerServiceInternal.class)).when(
+                () -> LocalServices.getService(PermissionManagerServiceInternal.class));
+        doReturn(mock(ActivityManagerInternal.class)).when(
+                () -> LocalServices.getService(ActivityManagerInternal.class));
+        doReturn(mock(UserManagerInternal.class)).when(
+                () -> LocalServices.getService(UserManagerInternal.class));
+        doReturn(mock(ActivityTaskManagerInternal.class)).when(
+                () -> LocalServices.getService(ActivityTaskManagerInternal.class));
+        doReturn(mock(LegacyPermissionManagerInternal.class)).when(
+                () -> LocalServices.getService(LegacyPermissionManagerInternal.class));
+        doReturn(mock(RoleManager.class)).when(mContext).getSystemService(RoleManager.class);
+        doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE);
+        doReturn(mApplicationInfo).when(mVoiceInteractionManagerServiceImpl).getApplicationInfo();
+
+        mVoiceInteractionManagerService = new VoiceInteractionManagerService(mContext);
+        mVoiceInteractionManagerServiceStub =
+                mVoiceInteractionManagerService.new VoiceInteractionManagerServiceStub();
+        mVoiceInteractionManagerServiceStub.mImpl = mVoiceInteractionManagerServiceImpl;
+        mPermissionEnforcer.grant(Manifest.permission.MANAGE_HOTWORD_DETECTION);
+    }
+
+    @Test
+    public void setIsReceiveSandboxedTrainingDataAllowed_currentAndPreinstalledAssistant_setsOp() {
+        // Set application info so current app is the current and preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+                /* allowed= */ true);
+
+        verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(),
+                mOpModeCaptor.capture());
+        assertThat(mOpIdCaptor.getValue()).isEqualTo(
+                AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA);
+        assertThat(mOpModeCaptor.getValue()).isEqualTo(AppOpsManager.MODE_ALLOWED);
+        assertThat(mUidCaptor.getValue()).isEqualTo(Process.myUid());
+    }
+
+    @Test
+    public void setIsReceiveSandboxedTrainingDataAllowed_missingPermission_doesNotSetOp() {
+        // Set application info so current app is the current and preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        // Simulate missing MANAGE_HOTWORD_DETECTION permission.
+        mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION);
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+                        /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void setIsReceiveSandboxedTrainingDataAllowed_notPreinstalledAssistant_doesNotSetOp() {
+        // Set application info so current app is not preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM.
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+                                /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void setIsReceiveSandboxedTrainingDataAllowed_notCurrentAssistant_doesNotSetOp() {
+        // Set application info so current app is not current assistant.
+        mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID.
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+                                /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 71d2504..dfe79bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -993,7 +993,9 @@
         dc.getDisplayPolicy().getDecorInsetsInfo(ROTATION_0, dc.mBaseDisplayHeight,
                 dc.mBaseDisplayWidth).mConfigFrame.set(0, 0, 1000, 990);
         dc.computeScreenConfiguration(config, ROTATION_0);
+        dc.onRequestedOverrideConfigurationChanged(config);
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation);
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation());
     }
 
     @Test
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 c0a90b2..e77c14a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -19,10 +19,13 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -62,9 +65,11 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -110,6 +115,8 @@
                 any(SurfaceControl.class), anyInt());
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -140,6 +147,8 @@
                 appWindow.getSurfaceControl(), 1);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -175,8 +184,17 @@
                 appWindow.getSurfaceControl(), 0);
         verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -202,8 +220,17 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 2);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -229,6 +256,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -256,6 +285,14 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -283,6 +320,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -310,5 +349,13 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 }
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 49a8886..c9a83b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
 import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
@@ -69,15 +70,20 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     // Parcel and Unparcel the LayoutParams in the window state to test the path the object
     // travels from the app's process to system server
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 7eab06a..89cd726 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1240,7 +1240,7 @@
         final ActivityRecord activity1 = finishTopActivity(rootTask1);
         assertEquals(DESTROYING, activity1.getState());
         verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
-                eq(display.mDisplayId), anyBoolean(), anyBoolean());
+                eq(display.mDisplayId), anyBoolean());
     }
 
     private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 0608cf4..28fecd6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -838,7 +838,7 @@
                         .setSystemDecorations(true).build();
 
         doReturn(true).when(mRootWindowContainer)
-                .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean());
+                .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
         doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
                 anyBoolean());
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3d2340c..72db7fe 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2278,6 +2278,12 @@
                         "Only the system or holders of the REPORT_USAGE_STATS"
                             + " permission are allowed to call reportUserInteraction");
                 }
+                if (userId != UserHandle.getCallingUserId()) {
+                    // Cross-user event reporting.
+                    getContext().enforceCallingPermission(
+                            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                            "Caller doesn't have INTERACT_ACROSS_USERS_FULL permission");
+                }
             } else {
                 if (!isCallingUidSystem()) {
                     throw new SecurityException("Only system is allowed to call"
@@ -2287,7 +2293,8 @@
 
             // Verify if this package exists before reporting an event for it.
             if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
-                throw new IllegalArgumentException("Package " + packageName + "not exist!");
+                throw new IllegalArgumentException("Package " + packageName
+                        + " does not exist!");
             }
 
             final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index b214591..6e4f13a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2273,9 +2273,9 @@
 
         private boolean isCallerPreinstalledAssistant() {
             return mImpl != null
-                    && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid()
-                    && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp()
-                    || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp());
+                    && mImpl.getApplicationInfo().uid == Binder.getCallingUid()
+                    && (mImpl.getApplicationInfo().isSystemApp()
+                    || mImpl.getApplicationInfo().isUpdatedSystemApp());
         }
 
         private void setImplLocked(VoiceInteractionManagerServiceImpl impl) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3c4b58f..7e0cbad 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -40,6 +40,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
@@ -540,6 +541,10 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
+    public ApplicationInfo getApplicationInfo() {
+        return mInfo.getServiceInfo().applicationInfo;
+    }
+
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a77b98..7cb2cc3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9892,7 +9892,6 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
-     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index a004cc3..2506360 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.WorkerThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.text.Editable;
@@ -39,6 +40,9 @@
  * </ul>
  * <p>
  * The formatting will be restarted once the text is cleared.
+ *
+ * @deprecated This is a thin wrapper on a `libphonenumber` `AsYouTypeFormatter`; it is recommended
+ * to use that instead.
  */
 public class PhoneNumberFormattingTextWatcher implements TextWatcher {
 
@@ -69,6 +73,7 @@
      * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
      * where the phone number is being entered.
      */
+    @WorkerThread
     public PhoneNumberFormattingTextWatcher(String countryCode) {
         if (countryCode == null) throw new IllegalArgumentException();
         mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e12a815..326b6f5 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -109,10 +109,6 @@
  * Then for SDK 35+, if the caller identity is personal profile, then
  * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
  *
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
- * may be required as documented on the each API.
- *
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1512,8 +1508,14 @@
     public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
         if (listener == null) return;
 
-        addOnSubscriptionsChangedListener(
-                new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener);
+        Looper looper = listener.getCreatorLooper();
+        if (looper == null) {
+            throw new RuntimeException(
+                    "Can't create handler inside thread " + Thread.currentThread()
+                    + " that has not called Looper.prepare()");
+        }
+
+        addOnSubscriptionsChangedListener(new HandlerExecutor(new Handler(looper)), listener);
     }
 
     /**
@@ -1815,9 +1817,6 @@
      * Then for SDK 35+, if the caller identity is personal profile, then this will return
      * subscription 1 only and vice versa.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
      * {@link SubscriptionInfo#getSubscriptionId}.
      *
@@ -2085,9 +2084,6 @@
      * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
      * it does today.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b96914e..f206987 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18009,6 +18009,64 @@
     }
 
     /**
+     * Enable or disable notifications sent for cellular identifier disclosure events.
+     *
+     * Disclosure events are defined as instances where a device has sent a cellular identifier
+     * on the Non-access stratum (NAS) before a security context is established. As a result the
+     * identifier is sent in the clear, which has privacy implications for the user.
+     *
+     * @param enable if notifications about disclosure events should be enabled
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support this feature.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY)
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public void enableCellularIdentifierDisclosureNotifications(boolean enable) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.enableCellularIdentifierDisclosureNotifications(enable);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "enableCellularIdentifierDisclosureNotifications RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get whether or not cellular identifier disclosure notifications are enabled.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support this feature.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY)
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public boolean isCellularIdentifierDisclosureNotificationEnabled() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isCellularIdentifierDisclosureNotificationEnabled();
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "isCellularIdentifierDisclosureNotificationEnabled RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Get current cell broadcast message identifier ranges.
      *
      * @throws SecurityException if the caller does not have the required permission
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 4c53f8a..d7d28a1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3159,4 +3159,37 @@
      * @return {@code true} if the operation is successful, {@code false} otherwise.
      */
     boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode);
+
+    /**
+     * Enable or disable notifications sent for cellular identifier disclosure events.
+     *
+     * Disclosure events are defined as instances where a device has sent a cellular identifier
+     * on the Non-access stratum (NAS) before a security context is established. As a result the
+     * identifier is sent in the clear, which has privacy implications for the user.
+     *
+     * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE</p>
+     *
+     * @param enabled if notifications about disclosure events should be enabled
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support this feature.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+        + "android.Manifest.permission.MODIFY_PHONE_STATE)")
+    void enableCellularIdentifierDisclosureNotifications(boolean enable);
+
+    /**
+     * Get whether or not cellular identifier disclosure notifications are enabled.
+     *
+     * <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE</p>
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support this feature.
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+        + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+    boolean isCellularIdentifierDisclosureNotificationEnabled();
 }
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 15a6a20..6fcdf1c 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -27,4 +27,7 @@
     name: "AaptSymlinkTest",
     sdk_version: "current",
     use_resource_processor: false,
+    compile_data: [
+        "targets/*",
+    ],
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
new file mode 100644
index 0000000..a135623
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.hoststubgen.nativesubstitution;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Native implementation substitutions for the LongArrayMultiStateCounter class.
+ */
+public class LongArrayMultiStateCounter_host {
+
+    /**
+     * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in
+     * Java instead of native.  The majority of the code (in C++) can be found in
+     * /frameworks/native/libs/battery/MultiStateCounter.h
+     */
+    private static class LongArrayMultiStateCounterRavenwood {
+        private final int mStateCount;
+        private final int mArrayLength;
+        private int mCurrentState;
+        private long mLastStateChangeTimestampMs = -1;
+        private long mLastUpdateTimestampMs = -1;
+        private boolean mEnabled = true;
+
+        private static class State {
+            private long mTimeInStateSinceUpdate;
+            private long[] mCounter;
+        }
+
+        private final State[] mStates;
+        private final long[] mValues;
+        private final long[] mDelta;
+
+        LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) {
+            mStateCount = stateCount;
+            mArrayLength = arrayLength;
+            mStates = new State[stateCount];
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i] = new State();
+                mStates[i].mCounter = new long[mArrayLength];
+            }
+            mValues = new long[mArrayLength];
+            mDelta = new long[mArrayLength];
+        }
+
+        public void setEnabled(boolean enabled, long timestampMs) {
+            if (enabled == mEnabled) {
+                return;
+            }
+
+            if (!enabled) {
+                setState(mCurrentState, timestampMs);
+                mEnabled = false;
+            } else {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (mLastStateChangeTimestampMs >= 0) {
+                    mLastStateChangeTimestampMs = timestampMs;
+                }
+                mEnabled = true;
+            }
+        }
+
+        public void setState(int state, long timestampMs) {
+            if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (timestampMs >= mLastStateChangeTimestampMs) {
+                    mStates[mCurrentState].mTimeInStateSinceUpdate +=
+                            timestampMs - mLastStateChangeTimestampMs;
+                } else {
+                    for (int i = 0; i < mStateCount; i++) {
+                        mStates[i].mTimeInStateSinceUpdate = 0;
+                    }
+                }
+            }
+            mCurrentState = state;
+            mLastStateChangeTimestampMs = timestampMs;
+        }
+
+        public void setValue(int state, long[] values) {
+            System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength);
+        }
+
+        public void updateValue(long[] values, long timestampMs) {
+            if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
+                if (timestampMs < mLastStateChangeTimestampMs) {
+                    timestampMs = mLastStateChangeTimestampMs;
+                }
+
+                setState(mCurrentState, timestampMs);
+
+                if (mLastUpdateTimestampMs >= 0) {
+                    if (timestampMs > mLastUpdateTimestampMs) {
+                        if (delta(mValues, values, mDelta)) {
+                            long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+                            for (int i = 0; i < mStateCount; i++) {
+                                long timeInState = mStates[i].mTimeInStateSinceUpdate;
+                                if (timeInState > 0) {
+                                    add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
+                                    mStates[i].mTimeInStateSinceUpdate = 0;
+                                }
+                            }
+                        } else {
+                            throw new RuntimeException();
+                        }
+                    } else if (timestampMs < mLastUpdateTimestampMs) {
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            System.arraycopy(values, 0, mValues, 0, mArrayLength);
+            mLastUpdateTimestampMs = timestampMs;
+        }
+
+        public void incrementValues(long[] delta, long timestampMs) {
+            long[] values = Arrays.copyOf(mValues, mValues.length);
+            for (int i = 0; i < mArrayLength; i++) {
+                values[i] += delta[i];
+            }
+            updateValue(values, timestampMs);
+        }
+
+        public void getValues(long[] values, int state) {
+            System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
+        }
+
+        public void reset() {
+            mLastStateChangeTimestampMs = -1;
+            mLastUpdateTimestampMs = -1;
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i].mTimeInStateSinceUpdate = 0;
+                Arrays.fill(mStates[i].mCounter, 0);
+            }
+        }
+
+        public void writeToParcel(Parcel parcel) {
+            parcel.writeInt(mStateCount);
+            parcel.writeInt(mArrayLength);
+            for (int i = 0; i < mStateCount; i++) {
+                parcel.writeLongArray(mStates[i].mCounter);
+            }
+        }
+
+        public void initFromParcel(Parcel parcel) {
+            try {
+                for (int i = 0; i < mStateCount; i++) {
+                    parcel.readLongArray(mStates[i].mCounter);
+                }
+            } catch (Exception e) {
+                throw new BadParcelableException(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int state = 0; state < mStateCount; state++) {
+                if (state != 0) {
+                    sb.append(", ");
+                }
+                sb.append(state).append(": {");
+                for (int i = 0; i < mStates[state].mCounter.length; i++) {
+                    if (i != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(mStates[state].mCounter[i]);
+                }
+                sb.append("}");
+            }
+            sb.append("]");
+            if (mLastUpdateTimestampMs >= 0) {
+                sb.append(" updated: ").append(mLastUpdateTimestampMs);
+            }
+            if (mLastStateChangeTimestampMs >= 0) {
+                sb.append(" currentState: ").append(mCurrentState);
+                if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) {
+                    sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs);
+                }
+            } else {
+                sb.append(" currentState: none");
+            }
+            return sb.toString();
+        }
+
+        private boolean delta(long[] values1, long[] values2, long[] delta) {
+            if (delta.length != mArrayLength) {
+                throw new RuntimeException();
+            }
+
+            boolean is_delta_valid = true;
+            for (int i = 0; i < mArrayLength; i++) {
+                if (values2[i] >= values1[i]) {
+                    delta[i] = values2[i] - values1[i];
+                } else {
+                    delta[i] = 0;
+                    is_delta_valid = false;
+                }
+            }
+
+            return is_delta_valid;
+        }
+
+        private void add(long[] counter, long[] delta, long numerator, long denominator) {
+            if (numerator != denominator) {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i] * numerator / denominator;
+                }
+            } else {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i];
+                }
+            }
+        }
+    }
+
+    public static class LongArrayContainer_host {
+        private static final HashMap<Long, long[]> sInstances = new HashMap<>();
+        private static long sNextId = 1;
+
+        public static long native_init(int arrayLength) {
+            long[] array = new long[arrayLength];
+            long instanceId = sNextId++;
+            sInstances.put(instanceId, array);
+            return instanceId;
+        }
+
+        static long[] getInstance(long instanceId) {
+            return sInstances.get(instanceId);
+        }
+
+        public static void native_setValues(long instanceId, long[] values) {
+            System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
+        }
+
+        public static void native_getValues(long instanceId, long[] values) {
+            System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
+        }
+
+        public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
+            long[] values = getInstance(instanceId);
+
+            boolean nonZero = false;
+            Arrays.fill(array, 0);
+
+            for (int i = 0; i < values.length; i++) {
+                int index = indexMap[i];
+                if (index < 0 || index >= array.length) {
+                    throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
+                                                        + (array.length - 1) + "]");
+                }
+                if (values[i] != 0) {
+                    array[index] += values[i];
+                    nonZero = true;
+                }
+            }
+            return nonZero;
+        }
+    }
+
+    private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances =
+            new HashMap<>();
+    private static long sNextId = 1;
+
+    public static long native_init(int stateCount, int arrayLength) {
+        LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood(
+                stateCount, arrayLength);
+        long instanceId = sNextId++;
+        sInstances.put(instanceId, instance);
+        return instanceId;
+    }
+
+    private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) {
+        return sInstances.get(instanceId);
+    }
+
+    public static void native_setEnabled(long instanceId, boolean enabled,
+            long timestampMs) {
+        getInstance(instanceId).setEnabled(enabled, timestampMs);
+    }
+
+    public static int native_getStateCount(long instanceId) {
+        return getInstance(instanceId).mStateCount;
+    }
+
+    public static int native_getArrayLength(long instanceId) {
+        return getInstance(instanceId).mArrayLength;
+    }
+
+    public static void native_setValues(long instanceId, int state, long containerInstanceId) {
+        getInstance(instanceId).setValue(state,
+                LongArrayContainer_host.getInstance(containerInstanceId));
+    }
+
+    public static void native_updateValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).updateValue(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_setState(long instanceId, int state, long timestampMs) {
+        getInstance(instanceId).setState(state, timestampMs);
+    }
+
+    public static void native_incrementValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).incrementValues(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
+        getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
+                state);
+    }
+
+    public static void native_reset(long instanceId) {
+        getInstance(instanceId).reset();
+    }
+
+    public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) {
+        getInstance(instanceId).writeToParcel(parcel);
+    }
+
+    public static long native_initFromParcel(Parcel parcel) {
+        int stateCount = parcel.readInt();
+        if (stateCount < 0 || stateCount > 0xEFFF) {
+            throw new BadParcelableException("stateCount out of range");
+        }
+        // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data.
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        int arrayLength = parcel.readInt();
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        long instanceId = native_init(stateCount, arrayLength);
+        getInstance(instanceId).initFromParcel(parcel);
+        if (parcel.dataPosition() > parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        return instanceId;
+    }
+
+    public static String native_toString(long instanceId) {
+        return getInstance(instanceId).toString();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index 3bcabcb..d63bff6 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -343,6 +343,28 @@
         p.mPos += length;
         p.updateSize();
     }
+    public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
+        var a = getInstance(thisNativePtr);
+        var b = getInstance(otherNativePtr);
+        if ((a.mSize == b.mSize) && Arrays.equals(a.mBuffer, b.mBuffer)) {
+            return 0;
+        } else {
+            return -1;
+        }
+    }
+    public static boolean nativeCompareDataInRange(
+            long ptrA, int offsetA, long ptrB, int offsetB, int length) {
+        var a = getInstance(ptrA);
+        var b = getInstance(ptrB);
+        if (offsetA < 0 || offsetA + length > a.mSize) {
+            throw new IllegalArgumentException();
+        }
+        if (offsetB < 0 || offsetB + length > b.mSize) {
+            throw new IllegalArgumentException();
+        }
+        return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length),
+                Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length));
+    }
     public static void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
         var dst = getInstance(thisNativePtr);