Merge "Change IAutofillManagerClient to IBinder for getCandidateCreds API" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index f330ad1..d13c4d7 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -75,16 +75,35 @@
     ],
 }
 
+java_library {
+    name: "mockito-ravenwood-prebuilt",
+    installable: false,
+    static_libs: [
+        "mockito-robolectric-prebuilt",
+    ],
+}
+
+java_library {
+    name: "inline-mockito-ravenwood-prebuilt",
+    installable: false,
+    static_libs: [
+        "inline-mockito-robolectric-prebuilt",
+    ],
+}
+
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
         "framework-minus-apex.ravenwood",
         "hoststubgen-helper-runtime.ravenwood",
         "hoststubgen-helper-framework-runtime.ravenwood",
+        "all-updatable-modules-system-stubs",
         "junit",
         "truth",
         "ravenwood-junit-impl",
         "android.test.mock.ravenwood",
+        "mockito-ravenwood-prebuilt",
+        "inline-mockito-ravenwood-prebuilt",
     ],
 }
 
@@ -94,5 +113,7 @@
         "junit",
         "truth",
         "ravenwood-junit",
+        "mockito-ravenwood-prebuilt",
+        "inline-mockito-ravenwood-prebuilt",
     ],
 }
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ecfd86c..d59775f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,14 +138,15 @@
     }
   ],
   "postsubmit-ravenwood": [
-    {
-      "name": "CtsUtilTestCasesRavenwood",
-      "host": true,
-      "file_patterns": [
-        "*Ravenwood*",
-        "*ravenwood*"
-      ]
-    }
+    // TODO(ravenwood) promote it to presubmit
+    // TODO: Enable it once the infra knows how to run it.
+//    {
+//      "name": "CtsUtilTestCasesRavenwood",
+//      "file_patterns": [
+//        "*Ravenwood*",
+//        "*ravenwood*"
+//      ]
+//    }
   ],
   "postsubmit-managedprofile-stress": [
     {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 74344cd..59c0128 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -239,6 +239,10 @@
     name: "android-non-updatable_from_source_defaults",
     libs: ["stub-annotations"],
     static_libs: ["framework-res-package-jar"], // Export package of framework-res
+}
+
+java_defaults {
+    name: "android-non-updatable_exportable_from_source_defaults",
     dist: {
         targets: ["sdk"],
         tag: ".jar",
@@ -265,6 +269,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -283,6 +295,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.system",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.system.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.module_lib",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -301,6 +321,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -319,6 +347,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.test",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.test.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.from-source",
     defaults: [
         "android-non-updatable_defaults",
@@ -326,6 +362,17 @@
     ],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: ["all-modules-public-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-public-stubs"],
     dist: {
         dir: "apistubs/android/public",
     },
@@ -339,6 +386,17 @@
     ],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.system.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/system",
     },
@@ -352,6 +410,17 @@
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable}"],
+    libs: non_updatable_api_deps_on_modules,
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -365,6 +434,17 @@
     ],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.test.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/test",
     },
@@ -462,6 +542,16 @@
 }
 
 java_library {
+    name: "android_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-public-stubs-exportable",
+        "android-non-updatable.stubs.exportable",
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["android.jar_defaults"],
+}
+
+java_library {
     name: "android_system_stubs_current.from-source",
     static_libs: [
         "all-modules-system-stubs",
@@ -470,6 +560,19 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-system-stubs-exportable",
+        "android-non-updatable.stubs.exportable.system",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -498,6 +601,23 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_test_stubs_current_exportable.from-source",
+    static_libs: [
+        // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+        // include the SystemApi of framework-non-updatable-sources.
+        "all-updatable-modules-system-stubs-exportable",
+        // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+        "all-non-updatable-modules-test-stubs-exportable",
+        "android-non-updatable.stubs.exportable.test",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -505,6 +625,7 @@
     },
 }
 
+// This module does not need to be copied to dist
 java_library {
     name: "android_test_frameworks_core_stubs_current.from-source",
     static_libs: [
@@ -513,24 +634,34 @@
     ],
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
-    dist: {
-        dir: "apistubs/android/test-core",
-    },
+    visibility: ["//frameworks/base/services"],
 }
 
 java_library {
     name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     static_libs: [
         "android-non-updatable.stubs.module_lib",
         "art.module.public.api.stubs.module_lib",
         "i18n.module.public.api.stubs",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_module_lib_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib",
+        "art.module.public.api.stubs.exportable.module_lib",
+        "i18n.module.public.api.stubs.exportable",
+    ],
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -540,13 +671,26 @@
     name: "android_system_server_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
         "android_module_lib_stubs_current.from-source",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_server_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    srcs: [":services-non-updatable-stubs{.exportable}"],
+    installable: false,
+    static_libs: [
+        "android_module_lib_stubs_current_exportable.from-source",
+    ],
     dist: {
         dir: "apistubs/android/system-server",
     },
diff --git a/api/api.go b/api/api.go
index b975c55..fa2be21 100644
--- a/api/api.go
+++ b/api/api.go
@@ -204,6 +204,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	// First create the all-updatable-modules-system-stubs
 	{
@@ -228,6 +237,30 @@
 	}
 }
 
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// First create the all-updatable-modules-system-stubs
+	{
+		updatable_modules := removeAll(modules, non_updatable_modules)
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+	// into all-modules-system-stubs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
@@ -237,6 +270,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
+	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
 	// This module is for the "framework-all" module, which should not include the core libraries.
 	modules = removeAll(modules, core_libraries_modules)
@@ -267,6 +309,19 @@
 	}
 }
 
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// The user of this module compiles against the "core" SDK and against non-updatable modules,
+	// so remove to avoid dupes.
+	modules = removeAll(modules, core_libraries_modules)
+	modules = removeAll(modules, non_updatable_modules)
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
@@ -382,6 +437,27 @@
 	}
 }
 
+func createFullExportableApiLibraries(ctx android.LoadHookContext) {
+	javaLibraryNames := []string{
+		"android_stubs_current_exportable",
+		"android_system_stubs_current_exportable",
+		"android_test_stubs_current_exportable",
+		"android_module_lib_stubs_current_exportable",
+		"android_system_server_stubs_current_exportable",
+	}
+
+	for _, libraryName := range javaLibraryNames {
+		props := libraryProps{}
+		props.Name = proptools.StringPtr(libraryName)
+		staticLib := libraryName + ".from-source"
+		props.Static_libs = []string{staticLib}
+		props.Defaults = []string{"android.jar_defaults"}
+		props.Visibility = []string{"//visibility:public"}
+
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
 	system_server_classpath := a.properties.System_server_classpath
@@ -397,6 +473,11 @@
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
+	createMergedPublicExportableStubs(ctx, bootclasspath)
+	createMergedSystemExportableStubs(ctx, bootclasspath)
+	createMergedTestExportableStubsForNonUpdatableModules(ctx)
+	createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
@@ -404,6 +485,8 @@
 	createApiContributionDefaults(ctx, bootclasspath)
 
 	createFullApiLibraries(ctx)
+
+	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 6ac6581..2f58f51 100644
--- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -38,4 +38,8 @@
     // Called when the display state of the device changes.
     // Where `displayState` is defined in AuthenticateOptions.DisplayState
     void onDisplayStateChanged(int displayState);
+
+    // Called when the HAL ignoring touches state changes.
+    // When true, the HAL ignores touches on the sensor.
+    void onHardwareIgnoreTouchesChanged(boolean shouldIgnore);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f974ef4..7d84bb3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1931,9 +1931,7 @@
      * A matching Activity will only be found if
      * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
      * <p>
-     * Input: Intent's data URI set with an application name, using the "package" schema (like
-     * "package:com.my.app").
-     * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+     * Input: The id of the rule, provided in the {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID} extra.
      * <p>
      * Output: Nothing.
      */
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 84c93c2..80061a5 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.os.FileUtils;
 
@@ -73,8 +72,7 @@
     @Test
     public void testReadNonexistentFile() throws Exception {
         mStoragedUidIoStatsReader.readAbsolute(mCallback);
-        verifyZeroInteractions(mCallback);
-
+        verifyNoMoreInteractions(mCallback);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 41f8204..aabc1cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -421,9 +421,9 @@
             }
             moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
 
-            if (!mHasLongClicked) {
+            if (!mHasLongClicked && id != R.id.maximize_window) {
                 final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
-                decoration.closeMaximizeMenu();
+                decoration.closeMaximizeMenuIfNeeded(e);
             }
 
             final long eventDuration = e.getEventTime() - e.getDownTime();
@@ -643,7 +643,7 @@
                 handleCaptionThroughStatusBar(ev, relevantDecor);
             }
         }
-        handleEventOutsideFocusedCaption(ev, relevantDecor);
+        handleEventOutsideCaption(ev, relevantDecor);
         // Prevent status bar from reacting to a caption drag.
         if (DesktopModeStatus.isEnabled()) {
             if (mTransitionDragActive) {
@@ -652,11 +652,17 @@
         }
     }
 
-    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
-    private void handleEventOutsideFocusedCaption(MotionEvent ev,
+    /**
+     * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+     * maximize the menu.
+     *
+     * @param relevantDecor the window decoration of the focused task's caption. This method only
+     *                      handles motion events outside this caption's bounds.
+     */
+    private void handleEventOutsideCaption(MotionEvent ev,
             DesktopModeWindowDecoration relevantDecor) {
         // Returns if event occurs within caption
-        if (relevantDecor == null || relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+        if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
             return;
         }
 
@@ -692,7 +698,7 @@
                     }
 
                     if (dragFromStatusBarAllowed
-                            && relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+                            && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
                         mTransitionDragActive = true;
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5f77192..53f806c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -612,8 +612,7 @@
     void closeMaximizeMenuIfNeeded(MotionEvent ev) {
         if (!isMaximizeMenuActive()) return;
 
-        final PointF inputPoint = offsetCaptionLocation(ev);
-        if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+        if (!mMaximizeMenu.isValidMenuInput(ev)) {
             closeMaximizeMenu();
         }
     }
@@ -639,20 +638,34 @@
     }
 
     /**
-     * Checks if motion event occurs in the caption handle area. This should be used in cases where
+     * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+     * a task in fullscreen or in multi-windowing mode). This should be used in cases where
      * onTouchListener will not work (i.e. when caption is in status bar area).
      *
      * @param ev       the {@link MotionEvent} to check
-     * @return {@code true} if event is inside the specified view, {@code false} if not
+     * @return {@code true} if event is inside caption handle view, {@code false} if not
      */
-    boolean checkTouchEventInCaptionHandle(MotionEvent ev) {
+    boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
         if (isHandleMenuActive() || !(mWindowDecorViewHolder
                 instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
             return false;
         }
+
+        return checkTouchEventInCaption(ev);
+    }
+
+    /**
+     * Checks if touch event occurs in caption.
+     *
+     * @param ev       the {@link MotionEvent} to check
+     * @return {@code true} if event is inside caption view, {@code false} if not
+     */
+    boolean checkTouchEventInCaption(MotionEvent ev) {
         final PointF inputPoint = offsetCaptionLocation(ev);
-        return ((DesktopModeFocusedWindowDecorationViewHolder) mWindowDecorViewHolder)
-                .pointInCaption(inputPoint, mResult.mCaptionX);
+        return inputPoint.x >= mResult.mCaptionX
+                && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+                && inputPoint.y >= 0
+                && inputPoint.y <= mResult.mCaptionHeight;
     }
 
     /**
@@ -668,7 +681,7 @@
             // Click if point in caption handle view
             final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
             final View handle = caption.findViewById(R.id.caption_handle);
-            if (checkTouchEventInCaptionHandle(ev)) {
+            if (checkTouchEventInFocusedCaptionHandle(ev)) {
                 mOnCaptionButtonClickListener.onClick(handle);
             }
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708f..794b357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -22,10 +22,10 @@
 import android.graphics.PixelFormat
 import android.graphics.PointF
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
 import android.view.SurfaceControlViewHost
-import android.view.View
 import android.view.View.OnClickListener
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
@@ -62,6 +62,8 @@
     private val cornerRadius = loadDimensionPixelSize(
             R.dimen.desktop_mode_maximize_menu_corner_radius
     ).toFloat()
+    private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+    private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
 
     /** Position the menu relative to the caption's position. */
     fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +97,6 @@
                 .setName("Maximize Menu")
                 .setContainerLayer()
                 .build()
-        val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
-        val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
         val lp = WindowManager.LayoutParams(
                 menuWidth,
                 menuHeight,
@@ -160,14 +160,11 @@
      *
      * @param inputPoint the input to compare against.
      */
-    fun isValidMenuInput(inputPoint: PointF): Boolean {
-        val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
-        return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
-                inputPoint.y - menuPosition.y)
-    }
-
-    private fun pointInView(v: View, x: Float, y: Float): Boolean {
-        return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+    fun isValidMenuInput(ev: MotionEvent): Boolean {
+        val x = ev.rawX
+        val y = ev.rawY
+        return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+                menuPosition.y <= y && menuPosition.y + menuHeight >= y)
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6a9258c..afe837e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -279,11 +279,12 @@
         }
 
         outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
-        final int captionWidth = params.mCaptionWidthId != Resources.ID_NULL
+        outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
                 ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
-        outResult.mCaptionX = (outResult.mWidth - captionWidth) / 2;
+        outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
 
-        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+                        outResult.mCaptionHeight)
                 .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
@@ -356,7 +357,7 @@
         // Caption view
         mCaptionWindowManager.setConfiguration(taskConfig);
         final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+                new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
                         WindowManager.LayoutParams.TYPE_APPLICATION,
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -578,6 +579,7 @@
 
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
         int mCaptionHeight;
+        int mCaptionWidth;
         int mCaptionX;
         int mWidth;
         int mHeight;
@@ -587,6 +589,7 @@
             mWidth = 0;
             mHeight = 0;
             mCaptionHeight = 0;
+            mCaptionWidth = 0;
             mCaptionX = 0;
             mRootView = null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 5f77022..6dcae27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -5,7 +5,6 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.res.ColorStateList
 import android.graphics.Color
-import android.graphics.PointF
 import android.view.View
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 import android.widget.ImageButton
@@ -47,17 +46,6 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
-    /**
-     * Returns true if input point is in the caption's view.
-     * @param inputPoint the input point relative to the task in full "focus" (i.e. fullscreen).
-     */
-    fun pointInCaption(inputPoint: PointF, captionX: Int): Boolean {
-        return inputPoint.x >= captionX &&
-                inputPoint.x <= captionX + captionView.width &&
-                inputPoint.y >= 0 &&
-                inputPoint.y <= captionView.height
-    }
-
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 991fe41..c292b502 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -7,19 +7,14 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_app {
-    name: "CredentialManager",
-    defaults: ["platform_app_defaults"],
-    certificate: "platform",
+android_library {
+    name: "CredentialManager-handheld",
+
+    platform_apis: true,
+
     srcs: ["src/**/*.kt"],
     resource_dirs: ["res"],
 
-    dex_preopt: {
-        profile_guided: true,
-        //TODO: b/312357299 - Update baseline profile
-        profile: "profile.txt.prof",
-    },
-
     static_libs: [
         "CredentialManagerShared",
         "PlatformComposeCore",
@@ -42,6 +37,23 @@
         "androidx.recyclerview_recyclerview",
         "kotlinx-coroutines-core",
     ],
+}
+
+android_app {
+    name: "CredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+
+    dex_preopt: {
+        profile_guided: true,
+        //TODO: b/312357299 - Update baseline profile
+        profile: "profile.txt.prof",
+    },
+
+    // Do not add new dependencies here. Add to CredentialManager-handheld instead.
+    static_libs: [
+        "CredentialManager-handheld",
+    ],
 
     platform_apis: true,
     privileged: true,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index c409ba6..f8ffc9e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -34,6 +34,7 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.compose.theme.PlatformTheme
 import com.android.credentialmanager.common.Constants
 import com.android.credentialmanager.common.DialogState
 import com.android.credentialmanager.common.ProviderActivityResult
@@ -43,7 +44,6 @@
 import com.android.credentialmanager.createflow.hasContentToDisplay
 import com.android.credentialmanager.getflow.GetCredentialScreen
 import com.android.credentialmanager.getflow.hasContentToDisplay
-import com.android.credentialmanager.ui.theme.PlatformTheme
 
 @ExperimentalMaterialApi
 class CredentialSelectorActivity : ComponentActivity() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index db69b8b..d319e4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -24,11 +24,11 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.rememberSystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import kotlinx.coroutines.launch
 
 
@@ -54,7 +54,7 @@
         setBottomSheetSystemBarsColor(sysUiController)
     }
     ModalBottomSheetLayout(
-        sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+        sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
         modifier = Modifier.background(Color.Transparent),
         sheetState = state,
         sheetContent = sheetContent,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 3976f9a..bdfe399 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -30,8 +30,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 /**
  * Container card for the whole sheet.
@@ -50,7 +50,7 @@
         modifier = modifier.fillMaxWidth().wrapContentHeight(),
         border = null,
         colors = CardDefaults.cardColors(
-            containerColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+            containerColor = LocalAndroidColorScheme.current.surfaceBright,
         ),
     ) {
         if (topAppBar != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 1c394ec..a6253b8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -56,9 +56,9 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.R
 import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.ui.theme.Shapes
 
 @Composable
@@ -168,7 +168,7 @@
                             // Decorative purpose only.
                             contentDescription = null,
                             modifier = Modifier.size(24.dp),
-                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         )
                     }
                 }
@@ -182,7 +182,7 @@
                         Icon(
                             modifier = iconSize,
                             bitmap = iconImageBitmap,
-                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                             // Decorative purpose only.
                             contentDescription = null,
                         )
@@ -206,7 +206,7 @@
                     Icon(
                         modifier = iconSize,
                         imageVector = iconImageVector,
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -218,7 +218,7 @@
                     Icon(
                         modifier = iconSize,
                         painter = iconPainter,
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -229,9 +229,9 @@
         },
         border = null,
         colors = SuggestionChipDefaults.suggestionChipColors(
-            containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
-            labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
-            iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+            containerColor = LocalAndroidColorScheme.current.surfaceContainerHigh,
+            labelColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+            iconContentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
         ),
     )
 }
@@ -294,7 +294,7 @@
         Icon(
             modifier = Modifier.size(24.dp),
             painter = leadingIconPainter,
-            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
             // Decorative purpose only.
             contentDescription = null,
         )
@@ -353,7 +353,7 @@
                             R.string.accessibility_back_arrow_button
                         ),
                         modifier = Modifier.size(24.dp).autoMirrored(),
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                     )
                 }
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 2df0c7a9..342af3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -24,20 +24,20 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
 
 @Composable
 fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
     InternalSectionHeader(
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         applyTopPadding = !isFirstSection
     )
 }
 
 @Composable
 fun MoreAboutPasskeySectionHeader(text: String) {
-    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
+    InternalSectionHeader(text, LocalAndroidColorScheme.current.onSurface)
 }
 
 @Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index a619523..b4075f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -19,8 +19,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import com.android.compose.SystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun setTransparentSystemBarsColor(sysUiController: SystemUiController) {
@@ -34,7 +34,7 @@
         darkIcons = false
     )
     sysUiController.setNavigationBarColor(
-        color = LocalAndroidColorScheme.current.colorSurfaceBright,
+        color = LocalAndroidColorScheme.current.surfaceBright,
         darkIcons = false
     )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 6b46636..9111e61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -25,7 +25,7 @@
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
  * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -37,7 +37,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         textAlign = TextAlign.Center,
         style = MaterialTheme.typography.headlineSmall,
     )
@@ -51,7 +51,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.bodyMedium,
     )
 }
@@ -69,7 +69,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.bodySmall,
         overflow = TextOverflow.Ellipsis,
         maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -85,7 +85,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         style = MaterialTheme.typography.titleLarge,
     )
 }
@@ -103,7 +103,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         style = MaterialTheme.typography.titleSmall,
         overflow = TextOverflow.Ellipsis,
         maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -159,7 +159,7 @@
         modifier = modifier.wrapContentSize(),
         text = text,
         textAlign = TextAlign.Center,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.labelLarge,
     )
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 14a9165..f261d1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
 import com.android.credentialmanager.model.EntryInfo
@@ -70,7 +71,6 @@
 import com.android.credentialmanager.logging.CreateCredentialEvent
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.creation.RemoteInfo
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -460,7 +460,7 @@
             item {
                 Divider(
                     thickness = 1.dp,
-                    color = LocalAndroidColorScheme.current.colorOutlineVariant,
+                    color = LocalAndroidColorScheme.current.outlineVariant,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index a291f59..458a99a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -26,7 +26,7 @@
 import com.android.internal.util.Preconditions
 
 data class GetCredentialUiState(
-        val isRequestForAllOptions: Boolean,
+    val isRequestForAllOptions: Boolean,
     val providerInfoList: List<ProviderInfo>,
     val requestDisplayInfo: RequestDisplayInfo,
     val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
@@ -165,7 +165,7 @@
     )
 }
 
-private fun toActiveEntry(
+fun toActiveEntry(
     providerDisplayInfo: ProviderDisplayInfo,
 ): EntryInfo? {
     val sortedUserNameToCredentialEntryList =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
deleted file mode 100644
index a33904d..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.ColorInt
-import android.content.Context
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-import com.android.internal.R
-
-/** File copied from PlatformComposeCore. */
-
-/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
-val LocalAndroidColorScheme =
-    staticCompositionLocalOf<AndroidColorScheme> {
-        throw IllegalStateException(
-            "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
-                "Composable surrounded by a PlatformTheme {}."
-        )
-    }
-
-/**
- * The Android color scheme.
- *
- * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
- * most of the colors in this class will be removed in favor of their M3 counterpart.
- */
-class AndroidColorScheme internal constructor(context: Context) {
-    val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
-    val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
-    val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
-    val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
-    val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
-
-    companion object {
-        fun getColor(context: Context, attr: Int): Color {
-            val ta = context.obtainStyledAttributes(intArrayOf(attr))
-            @ColorInt val color = ta.getColor(0, 0)
-            ta.recycle()
-            return Color(color)
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
deleted file mode 100644
index c923845..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.AttrRes
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-
-/** Read the [Color] from the given [attribute]. */
-@Composable
-@ReadOnlyComposable
-fun colorAttr(@AttrRes attribute: Int): Color {
-    return AndroidColorScheme.getColor(LocalContext.current, attribute)
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
deleted file mode 100644
index 2f1ce68..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens
-import com.android.credentialmanager.ui.theme.typography.TypefaceNames
-import com.android.credentialmanager.ui.theme.typography.TypefaceTokens
-import com.android.credentialmanager.ui.theme.typography.TypographyTokens
-import com.android.credentialmanager.ui.theme.typography.platformTypography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The Material 3 theme that should wrap all Platform Composables.
- *
- * TODO(b/280685309): Merge with the official SysUI platform theme.
- */
-@Composable
-fun PlatformTheme(
-    isDarkTheme: Boolean = isSystemInDarkTheme(),
-    content: @Composable () -> Unit,
-) {
-    val context = LocalContext.current
-
-    val colorScheme =
-        if (isDarkTheme) {
-            dynamicDarkColorScheme(context)
-        } else {
-            dynamicLightColorScheme(context)
-        }
-    val androidColorScheme = AndroidColorScheme(context)
-    val typefaceNames = remember(context) { TypefaceNames.get(context) }
-    val typography =
-        remember(typefaceNames) {
-            platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
-        }
-
-    MaterialTheme(colorScheme, typography = typography) {
-        CompositionLocalProvider(
-            LocalAndroidColorScheme provides androidColorScheme,
-        ) {
-            content()
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
deleted file mode 100644
index 984e4f1..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The typography for Platform Compose code.
- *
- * Do not use directly and call [MaterialTheme.typography] instead to access the different text
- * styles.
- */
-internal fun platformTypography(typographyTokens: TypographyTokens): Typography {
-    return Typography(
-        displayLarge = typographyTokens.displayLarge,
-        displayMedium = typographyTokens.displayMedium,
-        displaySmall = typographyTokens.displaySmall,
-        headlineLarge = typographyTokens.headlineLarge,
-        headlineMedium = typographyTokens.headlineMedium,
-        headlineSmall = typographyTokens.headlineSmall,
-        titleLarge = typographyTokens.titleLarge,
-        titleMedium = typographyTokens.titleMedium,
-        titleSmall = typographyTokens.titleSmall,
-        bodyLarge = typographyTokens.bodyLarge,
-        bodyMedium = typographyTokens.bodyMedium,
-        bodySmall = typographyTokens.bodySmall,
-        labelLarge = typographyTokens.labelLarge,
-        labelMedium = typographyTokens.labelMedium,
-        labelSmall = typographyTokens.labelSmall,
-    )
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
deleted file mode 100644
index b2dd207..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.unit.sp
-
-/** File copied from PlatformComposeCore. */
-internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
-    val bodyLargeFont = typefaceTokens.plain
-    val bodyLargeLineHeight = 24.0.sp
-    val bodyLargeSize = 16.sp
-    val bodyLargeTracking = 0.0.sp
-    val bodyLargeWeight = TypefaceTokens.WeightRegular
-    val bodyMediumFont = typefaceTokens.plain
-    val bodyMediumLineHeight = 20.0.sp
-    val bodyMediumSize = 14.sp
-    val bodyMediumTracking = 0.0.sp
-    val bodyMediumWeight = TypefaceTokens.WeightRegular
-    val bodySmallFont = typefaceTokens.plain
-    val bodySmallLineHeight = 16.0.sp
-    val bodySmallSize = 12.sp
-    val bodySmallTracking = 0.1.sp
-    val bodySmallWeight = TypefaceTokens.WeightRegular
-    val displayLargeFont = typefaceTokens.brand
-    val displayLargeLineHeight = 64.0.sp
-    val displayLargeSize = 57.sp
-    val displayLargeTracking = 0.0.sp
-    val displayLargeWeight = TypefaceTokens.WeightRegular
-    val displayMediumFont = typefaceTokens.brand
-    val displayMediumLineHeight = 52.0.sp
-    val displayMediumSize = 45.sp
-    val displayMediumTracking = 0.0.sp
-    val displayMediumWeight = TypefaceTokens.WeightRegular
-    val displaySmallFont = typefaceTokens.brand
-    val displaySmallLineHeight = 44.0.sp
-    val displaySmallSize = 36.sp
-    val displaySmallTracking = 0.0.sp
-    val displaySmallWeight = TypefaceTokens.WeightRegular
-    val headlineLargeFont = typefaceTokens.brand
-    val headlineLargeLineHeight = 40.0.sp
-    val headlineLargeSize = 32.sp
-    val headlineLargeTracking = 0.0.sp
-    val headlineLargeWeight = TypefaceTokens.WeightRegular
-    val headlineMediumFont = typefaceTokens.brand
-    val headlineMediumLineHeight = 36.0.sp
-    val headlineMediumSize = 28.sp
-    val headlineMediumTracking = 0.0.sp
-    val headlineMediumWeight = TypefaceTokens.WeightRegular
-    val headlineSmallFont = typefaceTokens.brand
-    val headlineSmallLineHeight = 32.0.sp
-    val headlineSmallSize = 24.sp
-    val headlineSmallTracking = 0.0.sp
-    val headlineSmallWeight = TypefaceTokens.WeightRegular
-    val labelLargeFont = typefaceTokens.plain
-    val labelLargeLineHeight = 20.0.sp
-    val labelLargeSize = 14.sp
-    val labelLargeTracking = 0.0.sp
-    val labelLargeWeight = TypefaceTokens.WeightMedium
-    val labelMediumFont = typefaceTokens.plain
-    val labelMediumLineHeight = 16.0.sp
-    val labelMediumSize = 12.sp
-    val labelMediumTracking = 0.1.sp
-    val labelMediumWeight = TypefaceTokens.WeightMedium
-    val labelSmallFont = typefaceTokens.plain
-    val labelSmallLineHeight = 16.0.sp
-    val labelSmallSize = 11.sp
-    val labelSmallTracking = 0.1.sp
-    val labelSmallWeight = TypefaceTokens.WeightMedium
-    val titleLargeFont = typefaceTokens.brand
-    val titleLargeLineHeight = 28.0.sp
-    val titleLargeSize = 22.sp
-    val titleLargeTracking = 0.0.sp
-    val titleLargeWeight = TypefaceTokens.WeightRegular
-    val titleMediumFont = typefaceTokens.plain
-    val titleMediumLineHeight = 24.0.sp
-    val titleMediumSize = 16.sp
-    val titleMediumTracking = 0.0.sp
-    val titleMediumWeight = TypefaceTokens.WeightMedium
-    val titleSmallFont = typefaceTokens.plain
-    val titleSmallLineHeight = 20.0.sp
-    val titleSmallSize = 14.sp
-    val titleSmallTracking = 0.0.sp
-    val titleSmallWeight = TypefaceTokens.WeightMedium
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
deleted file mode 100644
index 3cc761f..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalTextApi::class)
-
-package com.android.credentialmanager.ui.theme.typography
-
-import android.content.Context
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.font.DeviceFontFamilyName
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-
-/** File copied from PlatformComposeCore. */
-internal class TypefaceTokens(typefaceNames: TypefaceNames) {
-    companion object {
-        val WeightMedium = FontWeight.Medium
-        val WeightRegular = FontWeight.Normal
-    }
-
-    private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
-    private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
-
-    val brand =
-        FontFamily(
-            Font(brandFont, weight = WeightMedium),
-            Font(brandFont, weight = WeightRegular),
-        )
-    val plain =
-        FontFamily(
-            Font(plainFont, weight = WeightMedium),
-            Font(plainFont, weight = WeightRegular),
-        )
-}
-
-internal data class TypefaceNames
-private constructor(
-    val brand: String,
-    val plain: String,
-) {
-    private enum class Config(val configName: String, val default: String) {
-        Brand("config_headlineFontFamily", "sans-serif"),
-        Plain("config_bodyFontFamily", "sans-serif"),
-    }
-
-    companion object {
-        fun get(context: Context): TypefaceNames {
-            return TypefaceNames(
-                brand = getTypefaceName(context, Config.Brand),
-                plain = getTypefaceName(context, Config.Plain),
-            )
-        }
-
-        private fun getTypefaceName(context: Context, config: Config): String {
-            return context
-                .getString(context.resources.getIdentifier(config.configName, "string", "android"))
-                .takeIf { it.isNotEmpty() }
-                ?: config.default
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
deleted file mode 100644
index aadab92..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.text.TextStyle
-
-/** File copied from PlatformComposeCore. */
-internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
-    val bodyLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodyLargeFont,
-            fontWeight = typeScaleTokens.bodyLargeWeight,
-            fontSize = typeScaleTokens.bodyLargeSize,
-            lineHeight = typeScaleTokens.bodyLargeLineHeight,
-            letterSpacing = typeScaleTokens.bodyLargeTracking,
-        )
-    val bodyMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodyMediumFont,
-            fontWeight = typeScaleTokens.bodyMediumWeight,
-            fontSize = typeScaleTokens.bodyMediumSize,
-            lineHeight = typeScaleTokens.bodyMediumLineHeight,
-            letterSpacing = typeScaleTokens.bodyMediumTracking,
-        )
-    val bodySmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodySmallFont,
-            fontWeight = typeScaleTokens.bodySmallWeight,
-            fontSize = typeScaleTokens.bodySmallSize,
-            lineHeight = typeScaleTokens.bodySmallLineHeight,
-            letterSpacing = typeScaleTokens.bodySmallTracking,
-        )
-    val displayLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.displayLargeFont,
-            fontWeight = typeScaleTokens.displayLargeWeight,
-            fontSize = typeScaleTokens.displayLargeSize,
-            lineHeight = typeScaleTokens.displayLargeLineHeight,
-            letterSpacing = typeScaleTokens.displayLargeTracking,
-        )
-    val displayMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.displayMediumFont,
-            fontWeight = typeScaleTokens.displayMediumWeight,
-            fontSize = typeScaleTokens.displayMediumSize,
-            lineHeight = typeScaleTokens.displayMediumLineHeight,
-            letterSpacing = typeScaleTokens.displayMediumTracking,
-        )
-    val displaySmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.displaySmallFont,
-            fontWeight = typeScaleTokens.displaySmallWeight,
-            fontSize = typeScaleTokens.displaySmallSize,
-            lineHeight = typeScaleTokens.displaySmallLineHeight,
-            letterSpacing = typeScaleTokens.displaySmallTracking,
-        )
-    val headlineLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineLargeFont,
-            fontWeight = typeScaleTokens.headlineLargeWeight,
-            fontSize = typeScaleTokens.headlineLargeSize,
-            lineHeight = typeScaleTokens.headlineLargeLineHeight,
-            letterSpacing = typeScaleTokens.headlineLargeTracking,
-        )
-    val headlineMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineMediumFont,
-            fontWeight = typeScaleTokens.headlineMediumWeight,
-            fontSize = typeScaleTokens.headlineMediumSize,
-            lineHeight = typeScaleTokens.headlineMediumLineHeight,
-            letterSpacing = typeScaleTokens.headlineMediumTracking,
-        )
-    val headlineSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineSmallFont,
-            fontWeight = typeScaleTokens.headlineSmallWeight,
-            fontSize = typeScaleTokens.headlineSmallSize,
-            lineHeight = typeScaleTokens.headlineSmallLineHeight,
-            letterSpacing = typeScaleTokens.headlineSmallTracking,
-        )
-    val labelLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelLargeFont,
-            fontWeight = typeScaleTokens.labelLargeWeight,
-            fontSize = typeScaleTokens.labelLargeSize,
-            lineHeight = typeScaleTokens.labelLargeLineHeight,
-            letterSpacing = typeScaleTokens.labelLargeTracking,
-        )
-    val labelMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelMediumFont,
-            fontWeight = typeScaleTokens.labelMediumWeight,
-            fontSize = typeScaleTokens.labelMediumSize,
-            lineHeight = typeScaleTokens.labelMediumLineHeight,
-            letterSpacing = typeScaleTokens.labelMediumTracking,
-        )
-    val labelSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelSmallFont,
-            fontWeight = typeScaleTokens.labelSmallWeight,
-            fontSize = typeScaleTokens.labelSmallSize,
-            lineHeight = typeScaleTokens.labelSmallLineHeight,
-            letterSpacing = typeScaleTokens.labelSmallTracking,
-        )
-    val titleLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleLargeFont,
-            fontWeight = typeScaleTokens.titleLargeWeight,
-            fontSize = typeScaleTokens.titleLargeSize,
-            lineHeight = typeScaleTokens.titleLargeLineHeight,
-            letterSpacing = typeScaleTokens.titleLargeTracking,
-        )
-    val titleMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleMediumFont,
-            fontWeight = typeScaleTokens.titleMediumWeight,
-            fontSize = typeScaleTokens.titleMediumSize,
-            lineHeight = typeScaleTokens.titleMediumLineHeight,
-            letterSpacing = typeScaleTokens.titleMediumTracking,
-        )
-    val titleSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleSmallFont,
-            fontWeight = typeScaleTokens.titleSmallWeight,
-            fontSize = typeScaleTokens.titleSmallSize,
-            lineHeight = typeScaleTokens.titleSmallLineHeight,
-            letterSpacing = typeScaleTokens.titleSmallTracking,
-        )
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 6c75b434..0df9bac 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -37,6 +37,7 @@
         "androidx.preference_preference",
         "androidx.viewpager_viewpager",
         "SettingsLibDisplayUtils",
+        "SettingsLibSettingsTheme",
         "com_android_a11y_menu_flags_lib",
     ],
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..648cc3b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
             android:exported="true"
             android:label="@string/accessibility_menu_settings_name"
             android:launchMode="singleTop"
-            android:theme="@style/AccessibilityMenuSettings">
+            android:theme="@style/Theme.SettingsBase">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index eadcd7c..f5db6a4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -8,10 +8,3 @@
     description: "Hides the AccessibilityMenuService UI before taking action instead of after."
     bug: "292020123"
 }
-
-flag {
-    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
-    namespace: "accessibility"
-    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
-    bug: "298467628"
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
index 1f57654..81b3152 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
@@ -16,10 +16,6 @@
 -->
 
 <resources>
-  <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
-    <item name="android:windowLightStatusBar">false</item>
-  </style>
-
   <!--Adds the theme to support SnackBar component and user configurable theme. -->
   <style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight">
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a2508cd..4169155 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -16,11 +16,6 @@
 -->
 
 <resources>
-  <!--The theme is for preference CollapsingToolbarBaseActivity settings-->
-  <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
-    <item name="android:windowLightStatusBar">true</item>
-  </style>
-
   <!--Adds the theme to support SnackBar component and user configurable theme. -->
   <style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light">
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index bf51e23..ab8f97a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -35,7 +35,6 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 
 /**
@@ -56,28 +55,17 @@
 
         ActionBar actionBar = getActionBar();
         actionBar.setDisplayShowCustomEnabled(true);
-
-        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
+        actionBar.setDisplayHomeAsUpEnabled(true);
         actionBar.setCustomView(R.layout.preferences_action_bar);
         ((TextView) findViewById(R.id.action_bar_title)).setText(
                 getResources().getString(R.string.accessibility_menu_settings_name)
         );
-        actionBar.setDisplayOptions(
-                ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
-                        | ActionBar.DISPLAY_SHOW_TITLE
-                        | ActionBar.DISPLAY_HOME_AS_UP);
     }
 
     @Override
     public boolean onNavigateUp() {
-        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            mCallback.onBackInvoked();
-            return true;
-        } else {
-            return false;
-        }
+        mCallback.onBackInvoked();
+        return true;
     }
 
     /**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c4f372c..c2cf6e1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,7 +28,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -81,10 +80,8 @@
         if (convertView == null) {
             convertView = mInflater.inflate(R.layout.grid_item, parent, false);
 
-            if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-                configureShortcutSize(convertView,
-                        A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
-            }
+            configureShortcutSize(convertView,
+                    A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
         }
 
         A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -147,15 +144,6 @@
         ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
         TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
 
-        if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
-                ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
-                params.width = (int) (params.width * LARGE_BUTTON_SCALE);
-                params.height = (int) (params.height * LARGE_BUTTON_SCALE);
-                shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
-            }
-        }
-
         if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
             // Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
             shortcutIconButton.setImageResource(android.R.color.transparent);
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index d76f0ff..91a4d2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -88,8 +88,6 @@
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.res.R
 
 @Composable
@@ -576,10 +574,6 @@
     AndroidView(
         modifier = modifier,
         factory = {
-            viewModel.mediaHost.expansion = MediaHostState.EXPANDED
-            viewModel.mediaHost.showsOnlyActiveMedia = false
-            viewModel.mediaHost.falsingProtectionNeeded = false
-            viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
             viewModel.mediaHost.hostView.layoutParams =
                 FrameLayout.LayoutParams(
                     FrameLayout.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 02d30c5e..bc3ca1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -230,11 +230,7 @@
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
-        deviceEntryInteractor =
-            sceneTestUtils.deviceEntryInteractor(
-                authenticationInteractor = sceneTestUtils.authenticationInteractor(),
-                sceneInteractor = sceneInteractor,
-            )
+        deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor()
 
         mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest =
@@ -253,7 +249,7 @@
                 falsingManager,
                 userSwitcherController,
                 featureFlags,
-                sceneTestUtils.sceneContainerFlags,
+                sceneTestUtils.fakeSceneContainerFlags,
                 globalSettings,
                 sessionTracker,
                 Optional.of(sideFpsController),
@@ -791,6 +787,7 @@
     @Test
     fun dismissesKeyguard_whenSceneChangesToGone() =
         sceneTestUtils.testScope.runTest {
+            sceneTestUtils.fakeSceneContainerFlags.enabled = true
             // Upon init, we have never dismisses the keyguard.
             underTest.onInit()
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b9759cc..b4d4e1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -83,7 +83,7 @@
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
                 backgroundDispatcher = testUtils.testDispatcher,
-                flags = testUtils.sceneContainerFlags,
+                flags = testUtils.fakeSceneContainerFlags,
                 clock = clock,
                 getSecurityMode = getSecurityMode,
                 userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index b0beab9..f7743e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
@@ -118,6 +119,7 @@
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -174,7 +176,8 @@
                 mSelectedUserInteractor,
                 { deviceEntryUdfpsTouchOverlayViewModel },
                 { defaultUdfpsTouchOverlayViewModel },
-                shadeInteractor
+                shadeInteractor,
+                udfpsOverlayInteractor,
             )
         block()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a59a4b8..90c3c14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -76,6 +76,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -214,6 +215,8 @@
     @Mock
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
+    private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    @Mock
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
@@ -342,8 +345,8 @@
                 mFpsUnlockTracker,
                 mKeyguardTransitionInteractor,
                 mDeviceEntryUdfpsTouchOverlayViewModel,
-                mDefaultUdfpsTouchOverlayViewModel
-
+                mDefaultUdfpsTouchOverlayViewModel,
+                mUdfpsOverlayInteractor
         );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 13b53a8..7d9c2f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -27,6 +27,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -76,6 +77,7 @@
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     protected @Mock SelectedUserInteractor mSelectedUserInteractor;
     protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    protected @Mock UdfpsOverlayInteractor mUdfpsOverlayInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -152,7 +154,8 @@
                 mUdfpsKeyguardAccessibilityDelegate,
                 mSelectedUserInteractor,
                 mKeyguardTransitionInteractor,
-                mShadeInteractor);
+                mShadeInteractor,
+                mUdfpsOverlayInteractor);
         return controller;
     }
 }
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 335ac9d..0e257bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -24,6 +24,7 @@
 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.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -45,9 +47,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -130,6 +134,13 @@
                     repository = transitionRepository,
                 )
                 .keyguardTransitionInteractor
+        mUdfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                context,
+                mock(AuthController::class.java),
+                mock(SelectedUserInteractor::class.java),
+                testScope.backgroundScope,
+            )
         return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
@@ -239,6 +250,31 @@
         }
 
     @Test
+    fun shouldHandleTouchesChange() =
+        testScope.runTest {
+            val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches)
+
+            // GIVEN view is attached + on the keyguard
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+            whenever(mView.setPauseAuth(true)).thenReturn(true)
+            whenever(mView.unpausedAlpha).thenReturn(0)
+
+            // WHEN panelViewExpansion changes to expanded
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+            runCurrent()
+
+            // THEN UDFPS auth is paused and should not handle touches
+            assertThat(mController.shouldPauseAuth()).isTrue()
+            assertThat(shouldHandleTouches!!).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun fadeFromDialogSuggestedAlpha() =
         testScope.runTest {
             // GIVEN view is attached and status bar expansion is 1f
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 67ce86b..63581b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -16,27 +16,25 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
-import android.app.ActivityTaskManager
 import android.telecom.TelecomManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.activityTaskManager
 import com.android.internal.R
+import com.android.internal.logging.fakeMetricsLogger
 import com.android.internal.logging.nano.MetricsProto
-import com.android.internal.logging.testing.FakeMetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
+import com.android.telecom.telecomManager
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -53,27 +51,26 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerActionButtonInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var activityTaskManager: ActivityTaskManager
-    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var telecomManager: TelecomManager
 
-    private lateinit var utils: SceneTestUtils
-    private lateinit var testScope: TestScope
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val metricsLogger = utils.kosmos.fakeMetricsLogger
+    private val activityTaskManager = utils.kosmos.activityTaskManager
+    private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
 
-    private val metricsLogger = FakeMetricsLogger()
     private var currentUserId: Int = 0
     private var needsEmergencyAffordance = true
 
-    private lateinit var underTest: BouncerActionButtonInteractor
-
     @Before
     fun setUp() {
-        utils = SceneTestUtils(this)
-        testScope = utils.testScope
         MockitoAnnotations.initMocks(this)
+        utils.fakeSceneContainerFlags.enabled = true
+
+        mobileConnectionsRepository = utils.mobileConnectionsRepository
 
         overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
         overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -86,34 +83,18 @@
             .thenReturn(needsEmergencyAffordance)
         whenever(telecomManager.isInCall).thenReturn(false)
 
-        utils.featureFlags.set(REFACTOR_GETCURRENTUSER, true)
-
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+        utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true)
 
         utils.telephonyRepository.setHasTelephonyRadio(true)
 
-        underTest =
-            utils.bouncerActionButtonInteractor(
-                mobileConnectionsRepository = mobileConnectionsRepository,
-                activityTaskManager = activityTaskManager,
-                telecomManager = telecomManager,
-                emergencyAffordanceManager = emergencyAffordanceManager,
-                metricsLogger = metricsLogger,
-            )
+        utils.kosmos.telecomManager = telecomManager
     }
 
     @Test
     fun noTelephonyRadio_noButton() =
         testScope.runTest {
             utils.telephonyRepository.setHasTelephonyRadio(false)
-            underTest =
-                utils.bouncerActionButtonInteractor(
-                    mobileConnectionsRepository = mobileConnectionsRepository,
-                    activityTaskManager = activityTaskManager,
-                    telecomManager = telecomManager,
-                )
-
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -121,12 +102,8 @@
     @Test
     fun noTelecomManager_noButton() =
         testScope.runTest {
-            underTest =
-                utils.bouncerActionButtonInteractor(
-                    mobileConnectionsRepository = mobileConnectionsRepository,
-                    activityTaskManager = activityTaskManager,
-                    telecomManager = null,
-                )
+            utils.kosmos.telecomManager = null
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -134,6 +111,7 @@
     @Test
     fun duringCall_returnToCallButton() =
         testScope.runTest {
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             utils.telephonyRepository.setIsInCall(true)
 
@@ -143,6 +121,7 @@
             assertThat(actionButton?.onLongClick).isNull()
 
             actionButton?.onClick?.invoke()
+            runCurrent()
 
             assertThat(metricsLogger.logs.size).isEqualTo(1)
             assertThat(metricsLogger.logs.element().category)
@@ -154,6 +133,7 @@
     @Test
     fun noCall_secureAuthMethod_emergencyCallButton() =
         testScope.runTest {
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
@@ -165,6 +145,7 @@
             assertThat(actionButton?.onLongClick).isNotNull()
 
             actionButton?.onClick?.invoke()
+            runCurrent()
 
             assertThat(metricsLogger.logs.size).isEqualTo(1)
             assertThat(metricsLogger.logs.element().category)
@@ -182,10 +163,12 @@
     @Test
     fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
         testScope.runTest {
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
             utils.telephonyRepository.setIsInCall(false)
+            runCurrent()
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -196,6 +179,7 @@
     @Test
     fun noCall_insecure_noButton() =
         testScope.runTest {
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
@@ -207,6 +191,7 @@
     @Test
     fun noCall_simSecureButEmergencyNotSupported_noButton() =
         testScope.runTest {
+            val underTest = utils.bouncerActionButtonInteractor()
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
             overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 93ba6a4..4b6199b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -25,7 +25,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
@@ -37,8 +38,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -46,9 +45,7 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var mDeviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor
-
-    private val utils = SceneTestUtils(this)
+    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
 
@@ -57,6 +54,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
         overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
         overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
@@ -64,11 +62,7 @@
         overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
         overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
 
-        underTest =
-            utils.bouncerInteractor(
-                authenticationInteractor = authenticationInteractor,
-                deviceEntryFaceAuthInteractor = mDeviceEntryFaceAuthInteractor,
-            )
+        underTest = utils.bouncerInteractor()
     }
 
     @Test
@@ -305,8 +299,16 @@
     @Test
     fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
         testScope.runTest {
+            val isFaceAuthRunning by
+                collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+            utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+            runCurrent()
+            assertThat(isFaceAuthRunning).isTrue()
+
             underTest.onIntentionalUserInput()
-            verify(mDeviceEntryFaceAuthInteractor).onPrimaryBouncerUserInput()
+            runCurrent()
+
+            assertThat(isFaceAuthRunning).isFalse()
         }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f0843b..3043a71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -35,10 +35,7 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = utils.authenticationInteractor(),
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 47bbe6f4..4b1f9fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -41,6 +41,7 @@
 import kotlinx.coroutines.test.currentTime
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -52,15 +53,14 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val underTest =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
+    private lateinit var underTest: BouncerViewModel
+
+    @Before
+    fun setUp() {
+        utils.fakeSceneContainerFlags.enabled = true
+        underTest = utils.bouncerViewModel()
+    }
 
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -228,13 +228,13 @@
     fun isSideBySideSupported() =
         testScope.runTest {
             val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             utils.authenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
             utils.authenticationRepository.setAuthenticationMethod(Password)
             assertThat(isSideBySideSupported).isTrue()
 
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
             utils.authenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 64e6e57..5c5632f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -47,16 +47,8 @@
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
+    private val bouncerViewModel = utils.bouncerViewModel()
     private val isInputEnabled = MutableStateFlow(true)
 
     private val underTest =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 83d1938..9ee344a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -48,16 +48,8 @@
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
+    private val bouncerViewModel = utils.bouncerViewModel()
     private val underTest =
         PatternBouncerViewModel(
             applicationContext = context,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index db98d76..75e372f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -47,16 +47,8 @@
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
+    private val bouncerViewModel = utils.bouncerViewModel()
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
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 a776062..804c052 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
@@ -34,6 +34,7 @@
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.util.mockito.mock
@@ -48,6 +49,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -92,6 +94,13 @@
     }
 
     @Test
+    fun init_initsMediaHost() =
+        testScope.runTest {
+            // MediaHost is initialized as soon as the class is created.
+            verify(mediaHost).init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+        }
+
+    @Test
     fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
         testScope.runTest {
             // Keyguard showing, and tutorial not started.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index ea19cb7..929e879 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -23,9 +23,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -33,6 +32,7 @@
 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
 
@@ -43,19 +43,17 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
-    private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val trustRepository = FakeTrustRepository()
+    private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
+    private val trustRepository = utils.kosmos.fakeTrustRepository
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val underTest =
-        utils.deviceEntryInteractor(
-            repository = repository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-            faceAuthRepository = faceAuthRepository,
-            trustRepository = trustRepository,
-        )
+    private lateinit var underTest: DeviceEntryInteractor
+
+    @Before
+    fun setUp() {
+        utils.fakeSceneContainerFlags.enabled = true
+        underTest = utils.deviceEntryInteractor()
+    }
 
     @Test
     fun canSwipeToEnter_startsNull() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 11939c1..8933d2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -63,7 +63,7 @@
             repository = repository,
             commandQueue = commandQueue,
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            sceneContainerFlags = testUtils.sceneContainerFlags,
+            sceneContainerFlags = testUtils.fakeSceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
             shadeRepository = shadeRepository,
@@ -183,6 +183,7 @@
     @Test
     fun animationDozingTransitions() =
         testScope.runTest {
+            testUtils.fakeSceneContainerFlags.enabled = true
             val isAnimate by collectLastValue(underTest.animateDozingTransitions)
 
             underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 74d309c..04e90c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -87,11 +87,7 @@
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            deviceEntryInteractor =
-                utils.deviceEntryInteractor(
-                    authenticationInteractor = utils.authenticationInteractor(),
-                    sceneInteractor = utils.sceneInteractor(),
-                ),
+            deviceEntryInteractor = utils.deviceEntryInteractor(),
             communalInteractor = utils.communalInteractor(),
             longPress =
                 KeyguardLongPressViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 530d127d..ecc2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -35,13 +36,11 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -61,6 +60,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.telecom.telecomManager
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -101,27 +101,12 @@
 @RunWith(AndroidJUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
-    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
-    @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var telecomManager: TelecomManager
-
-    private val utils = SceneTestUtils(this)
+    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
     private val testScope = utils.testScope
     private val sceneContainerConfig = utils.fakeSceneContainerConfig()
-    private val sceneRepository =
-        utils.fakeSceneContainerRepository(
-            containerConfig = sceneContainerConfig,
-        )
-    private val sceneInteractor =
-        utils.sceneInteractor(
-            repository = sceneRepository,
-        )
+    private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
+    private val deviceEntryInteractor = utils.deviceEntryInteractor()
     private val communalInteractor = utils.communalInteractor()
 
     private val transitionState =
@@ -135,10 +120,7 @@
             )
             .apply { setTransitionState(transitionState) }
 
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
@@ -170,19 +152,15 @@
                     FakeMobileConnectionsRepository(),
                 ),
             constants = mock(),
-            utils.featureFlags,
+            utils.fakeFeatureFlags,
             scope = testScope.backgroundScope,
         )
 
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
     private lateinit var shadeSceneViewModel: ShadeSceneViewModel
 
-    private val keyguardRepository = utils.keyguardRepository
-    private val keyguardInteractor =
-        utils.keyguardInteractor(
-            repository = keyguardRepository,
-        )
-    private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+    private val keyguardInteractor = utils.keyguardInteractor()
+    private val powerInteractor = utils.powerInteractor()
 
     private var bouncerSceneJob: Job? = null
 
@@ -191,21 +169,26 @@
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var mediaHost: MediaHost
 
+    private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+    private lateinit var telecomManager: TelecomManager
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
+        telecomManager = checkNotNull(utils.kosmos.telecomManager)
         whenever(telecomManager.isInCall).thenReturn(false)
+        emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
         whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
 
-        utils.featureFlags.apply {
+        utils.fakeFeatureFlags.apply {
             set(Flags.NEW_NETWORK_SLICE_UI, false)
             set(Flags.REFACTOR_GETCURRENTUSER, true)
         }
 
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
-        mobileConnectionsRepository.isAnySimSecure.value = true
+        mobileConnectionsRepository = utils.mobileConnectionsRepository
+        mobileConnectionsRepository.isAnySimSecure.value = false
 
         utils.telephonyRepository.apply {
             setHasTelephonyRadio(true)
@@ -213,18 +196,8 @@
             setIsInCall(false)
         }
 
-        bouncerActionButtonInteractor =
-            utils.bouncerActionButtonInteractor(
-                mobileConnectionsRepository = mobileConnectionsRepository,
-                telecomManager = telecomManager,
-                emergencyAffordanceManager = emergencyAffordanceManager,
-            )
-        bouncerViewModel =
-            utils.bouncerViewModel(
-                bouncerInteractor = bouncerInteractor,
-                authenticationInteractor = authenticationInteractor,
-                actionButtonInteractor = bouncerActionButtonInteractor,
-            )
+        bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor()
+        bouncerViewModel = utils.bouncerViewModel()
 
         shadeHeaderViewModel =
             ShadeHeaderViewModel(
@@ -257,7 +230,7 @@
                 sceneInteractor = sceneInteractor,
                 deviceEntryInteractor = deviceEntryInteractor,
                 keyguardInteractor = keyguardInteractor,
-                flags = utils.sceneContainerFlags,
+                flags = utils.fakeSceneContainerFlags,
                 sysUiState = sysUiState,
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index ddeb05b..339d026 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -39,7 +39,7 @@
 @RunWith(AndroidJUnit4::class)
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
+    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
     private val testScope = utils.testScope
 
     @Test
@@ -72,7 +72,7 @@
     @Test(expected = IllegalStateException::class)
     fun setDesiredScene_noSuchSceneInContainer_throws() {
         utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-        val underTest = utils.fakeSceneContainerRepository(utils.fakeSceneContainerConfig())
+        val underTest = utils.fakeSceneContainerRepository()
         underTest.setDesiredScene(SceneModel(SceneKey.Shade))
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 7f4bbbe..486f7ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -28,6 +28,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -37,8 +38,14 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val repository = utils.fakeSceneContainerRepository()
-    private val underTest = utils.sceneInteractor(repository = repository)
+
+    private lateinit var underTest: SceneInteractor
+
+    @Before
+    fun setUp() {
+        utils.fakeSceneContainerFlags.enabled = true
+        underTest = utils.sceneInteractor()
+    }
 
     @Test
     fun allSceneKeys() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index dd22976..5fe4ca1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -68,17 +68,11 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val sceneContainerFlags = utils.sceneContainerFlags
+    private val sceneContainerFlags = utils.fakeSceneContainerFlags
     private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(authenticationInteractor = authenticationInteractor)
-    private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            faceAuthRepository = faceAuthRepository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
+    private val bouncerInteractor = utils.bouncerInteractor()
+    private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
+    private val deviceEntryInteractor = utils.deviceEntryInteractor()
     private val keyguardInteractor = utils.keyguardInteractor()
     private val sysUiState: SysUiState = mock()
     private val falsingCollector: FalsingCollector = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index c89cd9e..3a4ee64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -37,11 +38,17 @@
 
     private val utils = SceneTestUtils(this)
     private val interactor = utils.sceneInteractor()
-    private val underTest =
-        SceneContainerViewModel(
-            sceneInteractor = interactor,
-            falsingInteractor = utils.falsingInteractor(),
-        )
+    private lateinit var underTest: SceneContainerViewModel
+
+    @Before
+    fun setUp() {
+        utils.fakeSceneContainerFlags.enabled = true
+        underTest =
+            SceneContainerViewModel(
+                sceneInteractor = interactor,
+                falsingInteractor = utils.falsingInteractor(),
+            )
+    }
 
     @Test
     fun isVisible() = runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 1d2497d..a8133a3a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -56,12 +56,7 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
+    private val deviceEntryInteractor = utils.deviceEntryInteractor()
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 4cdb08a..607996d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -27,8 +27,7 @@
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -50,7 +49,7 @@
 
     private val kosmos =
         testKosmos().apply {
-            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+            fakeSceneContainerFlags.enabled = true
             fakeFeatureFlagsClassic.apply {
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
similarity index 74%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
rename to packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
index f9cdc1b..9f54c26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.model
+package com.android.systemui
 
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.sceneContainerConfig by
-    Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
+/** A [CoreStartable] that does nothing. */
+class NoOpCoreStartable : CoreStartable {
+    override fun start() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 7d9ec08..f5603ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,6 +23,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.systemui.Dumpable
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -49,7 +50,8 @@
     protected val statusBarStateController: StatusBarStateController,
     protected val shadeInteractor: ShadeInteractor,
     protected val dialogManager: SystemUIDialogManager,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : ViewController<T>(view), Dumpable {
 
     protected abstract val tag: String
@@ -130,11 +132,13 @@
     override fun onViewAttached() {
         dialogManager.registerListener(dialogListener)
         dumpManager.registerDumpable(dumpTag, this)
+        udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
     }
 
     override fun onViewDetached() {
         dialogManager.unregisterListener(dialogListener)
         dumpManager.unregisterDumpable(dumpTag)
+        udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
     }
 
     /**
@@ -165,6 +169,7 @@
     fun updatePauseAuth() {
         if (view.setPauseAuth(shouldPauseAuth())) {
             view.postInvalidate()
+            udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e7b0d9f..e0455b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -28,13 +29,15 @@
     statusBarStateController: StatusBarStateController,
     shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : UdfpsAnimationViewController<UdfpsBpView>(
     view,
     statusBarStateController,
     shadeInteractor,
     systemUIDialogManager,
-    dumpManager
+    dumpManager,
+    udfpsOverlayInteractor,
 ) {
     override val tag = "UdfpsBpViewController"
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 81de0a2..66fe4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -68,6 +68,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -171,6 +172,7 @@
     @NonNull private final Lazy<DefaultUdfpsTouchOverlayViewModel>
             mDefaultUdfpsTouchOverlayViewModel;
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @NonNull private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
     @NonNull private final InputManager mInputManager;
     @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
@@ -293,7 +295,8 @@
                         mSelectedUserInteractor,
                         mDeviceEntryUdfpsTouchOverlayViewModel,
                         mDefaultUdfpsTouchOverlayViewModel,
-                        mShadeInteractor
+                        mShadeInteractor,
+                        mUdfpsOverlayInteractor
                     )));
         }
 
@@ -674,7 +677,8 @@
             @NonNull FpsUnlockTracker fpsUnlockTracker,
             @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
             Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
-            Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel) {
+            Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel,
+            @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -715,6 +719,7 @@
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mShadeInteractor = shadeInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
         mSelectedUserInteractor = selectedUserInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index b94a177..4176083 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -45,6 +45,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder
 import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
@@ -109,6 +110,7 @@
     private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
     private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
     private val shadeInteractor: ShadeInteractor,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private var overlayViewLegacy: UdfpsView? = null
         private set
@@ -281,7 +283,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_KEYGUARD -> {
@@ -306,6 +309,7 @@
                     selectedUserInteractor,
                     transitionInteractor,
                     shadeInteractor,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_BP -> {
@@ -315,7 +319,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_OTHER,
@@ -325,7 +330,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index ab3fbb1..cfbbc26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -30,13 +31,15 @@
     statusBarStateController: StatusBarStateController,
     shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
     view,
     statusBarStateController,
     shadeInteractor,
     systemUIDialogManager,
-    dumpManager
+    dumpManager,
+    udfpsOverlayInteractor,
 ) {
     override val tag = "UdfpsFpmOtherViewController"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9f17024..7020d05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
@@ -75,6 +76,7 @@
     private val selectedUserInteractor: SelectedUserInteractor,
     private val transitionInteractor: KeyguardTransitionInteractor,
     shadeInteractor: ShadeInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -82,6 +84,7 @@
         shadeInteractor,
         systemUIDialogManager,
         dumpManager,
+        udfpsOverlayInteractor,
     ) {
     private val uniqueIdentifier = this.toString()
     private var showingUdfpsBouncer = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 863ba8d..70be0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -63,6 +63,9 @@
     /** Current display state, defined as [AuthenticateOptions.DisplayState] */
     val displayState: Flow<Int>
 
+    /** If touches on the fingerprint sensor should be ignored by the HAL. */
+    val isHardwareIgnoringTouches: Flow<Boolean>
+
     /**
      * Add a permanent context listener.
      *
@@ -79,12 +82,11 @@
     @Application private val applicationScope: CoroutineScope,
     private val foldProvider: FoldStateProvider,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : LogContextInteractor {
 
     init {
-        applicationScope.launch {
-            foldProvider.start()
-        }
+        applicationScope.launch { foldProvider.start() }
     }
 
     override val displayState =
@@ -102,6 +104,9 @@
             }
         }
 
+    override val isHardwareIgnoringTouches: Flow<Boolean> =
+        udfpsOverlayInteractor.shouldHandleTouches.map { shouldHandle -> !shouldHandle }
+
     override val isAod =
         displayState.map { it == AuthenticateOptions.DISPLAY_STATE_AOD }.distinctUntilChanged()
 
@@ -159,6 +164,12 @@
                 .catch { t -> Log.w(TAG, "failed to notify new display state", t) }
                 .launchIn(this)
 
+            isHardwareIgnoringTouches
+                .distinctUntilChanged()
+                .onEach { state -> listener.onHardwareIgnoreTouchesChanged(state) }
+                .catch { t -> Log.w(TAG, "failed to notify new set ignore state", t) }
+                .launchIn(this)
+
             listener.asBinder().linkToDeath({ cancel() }, 0)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index f4a2811..4fc1b58 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -30,8 +30,10 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 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.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -56,6 +58,16 @@
         return isUdfpsEnrolled && isWithinOverlayBounds
     }
 
+    /** Sets whether Udfps overlay should handle touches */
+    fun setHandleTouches(shouldHandle: Boolean = true) {
+        _shouldHandleTouches.value = shouldHandle
+    }
+
+    private var _shouldHandleTouches = MutableStateFlow(true)
+
+    /** Whether Udfps overlay should handle touches */
+    val shouldHandleTouches: StateFlow<Boolean> = _shouldHandleTouches.asStateFlow()
+
     /** Returns the current udfpsOverlayParams */
     val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 7a96fab..09c18ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -23,7 +23,9 @@
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.dagger.MediaModule
 import javax.inject.Inject
 import javax.inject.Named
@@ -70,6 +72,17 @@
     override val isPopupOnDismissCtaShowing: Flow<Boolean> =
         _isPopupOnDismissCtaShowing.asStateFlow()
 
+    init {
+        // Initialize our media host for the UMO. This only needs to happen once and must be done
+        // before the MediaHierarchyManager attempts to move the UMO to the hub.
+        with(mediaHost) {
+            expansion = MediaHostState.EXPANDED
+            showsOnlyActiveMedia = false
+            falsingProtectionNeeded = false
+            init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+        }
+    }
+
     override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
 
     override fun onDismissCtaTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 8c3e4a5..ab08f66 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -44,6 +44,7 @@
                 keyguardBottomAreaRefactor() &&
                 KeyguardShadeMigrationNssl.isEnabled &&
                 MediaInSceneContainerFlag.isEnabled &&
+                // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
                 ComposeFacade.isComposeAvailable()
 
     /**
@@ -63,6 +64,7 @@
             FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
             KeyguardShadeMigrationNssl.token,
             MediaInSceneContainerFlag.token,
+            // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
         )
 
     /** The full set of requirements for SceneContainer */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
new file mode 100644
index 0000000..cbd9887
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.NoOpCoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.logging.NotificationLogger.ExpansionStateLogger
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLoggerImpl
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Provider
+
+@Module
+interface NotificationStatsLoggerModule {
+
+    /** Binds an implementation to the [NotificationStatsLogger]. */
+    @Binds fun bindsStatsLoggerImpl(impl: NotificationStatsLoggerImpl): NotificationStatsLogger
+
+    companion object {
+
+        /** Provides a [NotificationStatsLogger] if the refactor flag is on. */
+        @Provides
+        fun provideStatsLogger(
+            provider: Provider<NotificationStatsLogger>
+        ): Optional<NotificationStatsLogger> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.of(provider.get())
+            } else {
+                Optional.empty()
+            }
+        }
+
+        /** Provides a [NotificationLoggerViewModel] if the refactor flag is on. */
+        @Provides
+        fun provideViewModel(
+            provider: Provider<NotificationLoggerViewModel>
+        ): Optional<NotificationLoggerViewModel> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.of(provider.get())
+            } else {
+                Optional.empty()
+            }
+        }
+
+        /** Provides the legacy [NotificationLogger] if the refactor flag is off. */
+        @Provides
+        @SysUISingleton
+        fun provideLegacyLoggerOptional(
+            notificationListener: NotificationListener?,
+            @UiBackground uiBgExecutor: Executor?,
+            notifLiveDataStore: NotifLiveDataStore?,
+            visibilityProvider: NotificationVisibilityProvider?,
+            notifPipeline: NotifPipeline?,
+            statusBarStateController: StatusBarStateController?,
+            windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor?,
+            javaAdapter: JavaAdapter?,
+            expansionStateLogger: ExpansionStateLogger?,
+            notificationPanelLogger: NotificationPanelLogger?
+        ): Optional<NotificationLogger> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.empty()
+            } else {
+                Optional.of(
+                    NotificationLogger(
+                        notificationListener,
+                        uiBgExecutor,
+                        notifLiveDataStore,
+                        visibilityProvider,
+                        notifPipeline,
+                        statusBarStateController,
+                        windowRootViewVisibilityInteractor,
+                        javaAdapter,
+                        expansionStateLogger,
+                        notificationPanelLogger
+                    )
+                )
+            }
+        }
+
+        /**
+         * Provides a the legacy [NotificationLogger] or the new [NotificationStatsLogger] to the
+         * notification row.
+         *
+         * TODO(b/308623704) remove the [NotificationRowStatsLogger] interface, and provide a
+         *   [NotificationStatsLogger] to the row directly.
+         */
+        @Provides
+        fun provideRowStatsLogger(
+            newProvider: Provider<NotificationStatsLogger>,
+            legacyLoggerOptional: Optional<NotificationLogger>,
+        ): NotificationRowStatsLogger {
+            return legacyLoggerOptional.getOrNull() ?: newProvider.get()
+        }
+
+        /**
+         * Binds the legacy [NotificationLogger] as a [CoreStartable] if the feature flag is off, or
+         * binds a no-op [CoreStartable] otherwise.
+         *
+         * The old [NotificationLogger] is a [CoreStartable], because it's managing its own data
+         * updates, but the new [NotificationStatsLogger] is not. Currently Dagger doesn't support
+         * optionally binding entries with @[IntoMap], therefore we provide a no-op [CoreStartable]
+         * here if the feature flag is on, but this can be removed once the flag is released.
+         */
+        @Provides
+        @IntoMap
+        @ClassKey(NotificationLogger::class)
+        fun provideCoreStartable(
+            legacyLoggerOptional: Optional<NotificationLogger>
+        ): CoreStartable {
+            return legacyLoggerOptional.getOrNull() ?: NoOpCoreStartable()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 3a72205..6bba72b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -17,14 +17,12 @@
 package com.android.systemui.statusbar.notification.dagger;
 
 import android.content.Context;
+import android.service.notification.NotificationListenerService;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -67,7 +65,6 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -80,7 +77,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.kotlin.JavaAdapter;
+
+import javax.inject.Provider;
 
 import dagger.Binds;
 import dagger.Module;
@@ -88,10 +86,6 @@
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
@@ -104,6 +98,7 @@
         NotificationSectionHeadersModule.class,
         ActivatableNotificationViewModelModule.class,
         NotificationMemoryModule.class,
+        NotificationStatsLoggerModule.class,
 })
 public interface NotificationsModule {
     @Binds
@@ -128,39 +123,6 @@
     VisibilityLocationProvider bindVisibilityLocationProvider(
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
 
-    /** Provides an instance of {@link NotificationLogger} */
-    @SysUISingleton
-    @Provides
-    static NotificationLogger provideNotificationLogger(
-            NotificationListener notificationListener,
-            @UiBackground Executor uiBgExecutor,
-            NotifLiveDataStore notifLiveDataStore,
-            NotificationVisibilityProvider visibilityProvider,
-            NotifPipeline notifPipeline,
-            StatusBarStateController statusBarStateController,
-            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
-            JavaAdapter javaAdapter,
-            NotificationLogger.ExpansionStateLogger expansionStateLogger,
-            NotificationPanelLogger notificationPanelLogger) {
-        return new NotificationLogger(
-                notificationListener,
-                uiBgExecutor,
-                notifLiveDataStore,
-                visibilityProvider,
-                notifPipeline,
-                statusBarStateController,
-                windowRootViewVisibilityInteractor,
-                javaAdapter,
-                expansionStateLogger,
-                notificationPanelLogger);
-    }
-
-    /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
-    @Binds
-    @IntoMap
-    @ClassKey(NotificationLogger.class)
-    CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
-
     /** Provides an instance of {@link NotificationPanelLogger} */
     @SysUISingleton
     @Provides
@@ -272,6 +234,10 @@
     NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
 
     /** */
+    @Binds
+    NotificationListenerService bindNotificationListener(NotificationListener notificationListener);
+
+    /** */
     @Provides
     @SysUISingleton
     static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 5ed82cc..5c844bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -55,6 +55,12 @@
      * invoking [get].
      */
     val renderList: List<Key> = emptyList(),
+
+    /**
+     * Map of notification key to rank, where rank is the 0-based index of the notification on the
+     * system server, meaning that in the unfiltered flattened list of notification entries.
+     */
+    val rankingsMap: Map<String, Int> = emptyMap()
 ) {
     operator fun get(key: Key): ActiveNotificationEntryModel? {
         return when (key) {
@@ -74,8 +80,9 @@
         private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
         private val individuals = mutableMapOf<String, ActiveNotificationModel>()
         private val renderList = mutableListOf<Key>()
+        private var rankingsMap: Map<String, Int> = emptyMap()
 
-        fun build() = ActiveNotificationsStore(groups, individuals, renderList)
+        fun build() = ActiveNotificationsStore(groups, individuals, renderList, rankingsMap)
 
         fun addEntry(entry: ActiveNotificationEntryModel) {
             when (entry) {
@@ -95,5 +102,9 @@
             individuals[group.summary.key] = group.summary
             group.children.forEach { individuals[it.key] = it }
         }
+
+        fun setRankingsMap(map: Map<String, Int>) {
+            rankingsMap = map.toMap()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index e90ddf9..ca6344d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -33,7 +33,10 @@
     private val repository: ActiveNotificationListRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
-    /** Notifications actively presented to the user in the notification stack, in order. */
+    /**
+     * Top level list of Notifications actively presented to the user in the notification stack, in
+     * order.
+     */
     val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
         repository.activeNotifications
             .map { store ->
@@ -51,6 +54,13 @@
             }
             .flowOn(backgroundDispatcher)
 
+    /**
+     * Flattened list of Notifications actively presented to the user in the notification stack, in
+     * order.
+     */
+    val allRepresentativeNotifications: Flow<Map<String, ActiveNotificationModel>> =
+        repository.activeNotifications.map { store -> store.individuals }
+
     /** Are any notifications being actively presented in the notification stack? */
     val areAnyNotificationsPresent: Flow<Boolean> =
         repository.activeNotifications
@@ -65,6 +75,16 @@
     val areAnyNotificationsPresentValue: Boolean
         get() = repository.activeNotifications.value.renderList.isNotEmpty()
 
+    /**
+     * Map of notification key to rank, where rank is the 0-based index of the notification in the
+     * system server, meaning that in the unfiltered flattened list of notification entries. Used
+     * for logging purposes.
+     *
+     * @see [com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger].
+     */
+    val activeNotificationRanks: Flow<Map<String, Int>> =
+        repository.activeNotifications.map { store -> store.rankingsMap }
+
     /** Are there are any notifications that can be cleared by the "Clear all" button? */
     val hasClearableNotifications: Flow<Boolean> =
         repository.notifStats
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 6f4ed9d..695f215 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.graphics.drawable.Icon
+import android.util.ArrayMap
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -46,6 +47,7 @@
         repository.activeNotifications.update { existingModels ->
             buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
                 entries.forEach(::addListEntry)
+                setRankingsMap(entries)
             }
         }
     }
@@ -94,6 +96,27 @@
         }
     }
 
+    fun setRankingsMap(entries: List<ListEntry>) {
+        builder.setRankingsMap(flatMapToRankingsMap(entries))
+    }
+
+    fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> {
+        val result = ArrayMap<String, Int>()
+        for (entry in entries) {
+            if (entry is NotificationEntry) {
+                entry.representativeEntry?.let { representativeEntry ->
+                    result[representativeEntry.key] = representativeEntry.ranking.rank
+                }
+            } else if (entry is GroupEntry) {
+                entry.summary?.let { summary -> result[summary.key] = summary.ranking.rank }
+                for (child in entry.children) {
+                    result[child.key] = child.ranking.rank
+                }
+            }
+        }
+        return result
+    }
+
     private fun NotificationEntry.toModel(): ActiveNotificationModel =
         existingModels.createOrReuse(
             key = key,
@@ -107,6 +130,11 @@
             aodIcon = icons.aodIcon?.sourceIcon,
             shelfIcon = icons.shelfIcon?.sourceIcon,
             statusBarIcon = icons.statusBarIcon?.sourceIcon,
+            uid = sbn.uid,
+            packageName = sbn.packageName,
+            instanceId = sbn.instanceId?.id,
+            isGroupSummary = sbn.notification.isGroupSummary,
+            bucket = bucket,
         )
 }
 
@@ -121,7 +149,12 @@
     isPulsing: Boolean,
     aodIcon: Icon?,
     shelfIcon: Icon?,
-    statusBarIcon: Icon?
+    statusBarIcon: Icon?,
+    uid: Int,
+    packageName: String,
+    instanceId: Int?,
+    isGroupSummary: Boolean,
+    bucket: Int,
 ): ActiveNotificationModel {
     return individuals[key]?.takeIf {
         it.isCurrent(
@@ -135,7 +168,12 @@
             isPulsing = isPulsing,
             aodIcon = aodIcon,
             shelfIcon = shelfIcon,
-            statusBarIcon = statusBarIcon
+            statusBarIcon = statusBarIcon,
+            uid = uid,
+            instanceId = instanceId,
+            isGroupSummary = isGroupSummary,
+            packageName = packageName,
+            bucket = bucket,
         )
     }
         ?: ActiveNotificationModel(
@@ -150,6 +188,11 @@
             aodIcon = aodIcon,
             shelfIcon = shelfIcon,
             statusBarIcon = statusBarIcon,
+            uid = uid,
+            instanceId = instanceId,
+            isGroupSummary = isGroupSummary,
+            packageName = packageName,
+            bucket = bucket,
         )
 }
 
@@ -164,7 +207,12 @@
     isPulsing: Boolean,
     aodIcon: Icon?,
     shelfIcon: Icon?,
-    statusBarIcon: Icon?
+    statusBarIcon: Icon?,
+    uid: Int,
+    packageName: String,
+    instanceId: Int?,
+    isGroupSummary: Boolean,
+    bucket: Int,
 ): Boolean {
     return when {
         key != this.key -> false
@@ -178,6 +226,11 @@
         aodIcon != this.aodIcon -> false
         shelfIcon != this.shelfIcon -> false
         statusBarIcon != this.statusBarIcon -> false
+        uid != this.uid -> false
+        instanceId != this.instanceId -> false
+        isGroupSummary != this.isGroupSummary -> false
+        packageName != this.packageName -> false
+        bucket != this.bucket -> false
         else -> true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 7d1cca8..ac49960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
@@ -53,7 +54,9 @@
  * any initialization work that notifications require.
  */
 @SysUISingleton
-class NotificationsControllerImpl @Inject constructor(
+class NotificationsControllerImpl
+@Inject
+constructor(
     private val notificationListener: NotificationListener,
     private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
@@ -61,7 +64,7 @@
     private val targetSdkResolver: TargetSdkResolver,
     private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
-    private val notificationLogger: NotificationLogger,
+    private val notificationLoggerOptional: Optional<NotificationLogger>,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val notificationsMediaManager: NotificationMediaManager,
     private val headsUpViewBinder: HeadsUpViewBinder,
@@ -80,28 +83,35 @@
     ) {
         notificationListener.registerAsSystemService()
 
-        notifPipeline.get().addCollectionListener(object : NotifCollectionListener {
-            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                listContainer.cleanUpViewStateForEntry(entry)
-            }
-        })
+        notifPipeline
+            .get()
+            .addCollectionListener(
+                object : NotifCollectionListener {
+                    override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                        listContainer.cleanUpViewStateForEntry(entry)
+                    }
+                }
+            )
 
         notificationRowBinder.setNotificationClicker(
-            clickerBuilder.build(bubblesOptional, notificationActivityStarter))
+            clickerBuilder.build(bubblesOptional, notificationActivityStarter)
+        )
         notificationRowBinder.setUpWithPresenter(presenter, listContainer)
         headsUpViewBinder.setPresenter(presenter)
         notifBindPipelineInitializer.initialize()
         animatedImageNotificationManager.bind()
 
-        notifPipelineInitializer.get().initialize(
-                notificationListener,
-                notificationRowBinder,
-                listContainer,
-                stackController)
+        notifPipelineInitializer
+            .get()
+            .initialize(notificationListener, notificationRowBinder, listContainer, stackController)
 
         targetSdkResolver.initialize(notifPipeline.get())
         notificationsMediaManager.setUpWithPresenter(presenter)
-        notificationLogger.setUpWithContainer(listContainer)
+        if (!NotificationsLiveDataStoreRefactor.isEnabled) {
+            notificationLoggerOptional.ifPresent { logger ->
+                logger.setUpWithContainer(listContainer)
+            }
+        }
         peopleSpaceWidgetManager.attach(notificationListener)
     }
 
@@ -120,11 +130,11 @@
             notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id)
         } else {
             notificationListener.snoozeNotification(
-                    sbn.key,
-                    snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong())
+                sbn.key,
+                snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()
+            )
         }
     }
 
-    override fun getActiveNotificationsCount(): Int =
-        notifLiveDataStore.activeNotifCount.value
+    override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 8d2a63e..4349b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.statusbar.NotificationVisibility.NotificationLocation;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -46,8 +47,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
@@ -64,7 +67,8 @@
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-public class NotificationLogger implements StateListener, CoreStartable {
+public class NotificationLogger implements StateListener, CoreStartable,
+        NotificationRowStatsLogger {
     static final String TAG = "NotificationLogger";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
@@ -166,31 +170,31 @@
     /**
      * Returns the location of the notification referenced by the given {@link NotificationEntry}.
      */
-    public static NotificationVisibility.NotificationLocation getNotificationLocation(
+    public static NotificationLocation getNotificationLocation(
             NotificationEntry entry) {
         if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
-            return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+            return NotificationLocation.LOCATION_UNKNOWN;
         }
         return convertNotificationLocation(entry.getRow().getViewState().location);
     }
 
-    private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+    private static NotificationLocation convertNotificationLocation(
             int location) {
         switch (location) {
             case ExpandableViewState.LOCATION_FIRST_HUN:
-                return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+                return NotificationLocation.LOCATION_FIRST_HEADS_UP;
             case ExpandableViewState.LOCATION_HIDDEN_TOP:
-                return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+                return NotificationLocation.LOCATION_HIDDEN_TOP;
             case ExpandableViewState.LOCATION_MAIN_AREA:
-                return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+                return NotificationLocation.LOCATION_MAIN_AREA;
             case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
-                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+                return NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
             case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
-                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+                return NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
             case ExpandableViewState.LOCATION_GONE:
-                return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+                return NotificationLocation.LOCATION_GONE;
             default:
-                return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+                return NotificationLocation.LOCATION_UNKNOWN;
         }
     }
 
@@ -207,6 +211,9 @@
             JavaAdapter javaAdapter,
             ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
+        // Not expected to be constructed if the feature flag is on
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
+
         mNotificationListener = notificationListener;
         mUiBgExecutor = uiBgExecutor;
         mNotifLiveDataStore = notifLiveDataStore;
@@ -382,9 +389,11 @@
     /**
      * Called when the notification is expanded / collapsed.
      */
-    public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
-        NotificationVisibility.NotificationLocation location = mVisibilityProvider.getLocation(key);
-        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
+    @Override
+    public void onNotificationExpansionChanged(@NonNull String key, boolean isExpanded,
+            int location, boolean isUserAction) {
+        NotificationLocation notifLocation = mVisibilityProvider.getLocation(key);
+        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, notifLocation);
     }
 
     @VisibleForTesting
@@ -440,7 +449,7 @@
 
         @VisibleForTesting
         void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
-                NotificationVisibility.NotificationLocation location) {
+                NotificationLocation location) {
             State state = getState(key);
             state.mIsUserAction = isUserAction;
             state.mIsExpanded = isExpanded;
@@ -528,7 +537,7 @@
             @Nullable
             Boolean mIsVisible;
             @Nullable
-            NotificationVisibility.NotificationLocation mLocation;
+            NotificationLocation mLocation;
 
             private State() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 5ca13c9..6c63d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -40,6 +40,11 @@
 
     /**
      * Log a NOTIFICATION_PANEL_REPORTED statsd event.
+     */
+    void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto);
+
+    /**
+     * Log a NOTIFICATION_PANEL_REPORTED statsd event.
      * @param visibleNotifications as provided by NotificationEntryManager.getVisibleNotifications()
      */
     void logPanelShown(boolean isLockscreen,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 9a63228..d7f7b76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -21,6 +21,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 
 import com.google.protobuf.nano.MessageNano;
 
@@ -31,15 +32,25 @@
  * Normal implementation of NotificationPanelLogger.
  */
 public class NotificationPanelLoggerImpl implements NotificationPanelLogger {
+
+    @Override
+    public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+        SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+                /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
+    }
+
     @Override
     public void logPanelShown(boolean isLockscreen,
             List<NotificationEntry> visibleNotifications) {
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
         final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
                 visibleNotifications);
         SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
-                /* int event_id */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
-                /* int num_notifications*/ proto.notifications.length,
-                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+                /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
     }
 
     @Override
@@ -47,8 +58,8 @@
         final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
                 Collections.singletonList(draggedNotification));
         SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
-                /* int event_id */ NOTIFICATION_DRAG.getId(),
-                /* int num_notifications*/ proto.notifications.length,
-                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+                /* event_id = */ NOTIFICATION_DRAG.getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index e200e65..5eeb066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -118,6 +118,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
@@ -988,6 +989,25 @@
     }
 
     /**
+     * Recursively collects the [{@link ExpandableViewState#location}]s populating the provided
+     * map.
+     * The visibility of each child is determined by the {@link View#getVisibility()}.
+     * Locations are added to the provided map including locations from child views, that are
+     * visible.
+     */
+    public void collectVisibleLocations(Map<String, Integer> locationsMap) {
+        if (getVisibility() == View.VISIBLE) {
+            locationsMap.put(getEntry().getKey(), getViewState().location);
+            if (mChildrenContainer != null) {
+                List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
+                for (int i = 0; i < children.size(); i++) {
+                    children.get(i).collectVisibleLocations(locationsMap);
+                }
+            }
+        }
+    }
+
+    /**
      * Updates states of all children.
      */
     public void updateChildrenStates() {
@@ -1615,7 +1635,8 @@
         /**
          * Called when the notification is expanded / collapsed.
          */
-        void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+        void logNotificationExpansion(String key, int location, boolean userAction,
+                boolean expanded);
 
         /**
          * Called when a notification which was previously kept in its parent for the
@@ -3312,7 +3333,8 @@
         if (nowExpanded != wasExpanded) {
             updateShelfIconColor();
             if (mLogger != null) {
-                mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
+                mLogger.logNotificationExpansion(mLoggingKey, getViewState().location, userAction,
+                        nowExpanded);
             }
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.onExpansionChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index af55f44..0afdefa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -48,13 +48,13 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -90,7 +90,7 @@
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private final RowContentBindStage mRowContentBindStage;
-    private final NotificationLogger mNotificationLogger;
+    private final NotificationRowStatsLogger mStatsLogger;
     private final NotificationRowLogger mLogBufferLogger;
     private final HeadsUpManager mHeadsUpManager;
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
@@ -130,9 +130,10 @@
     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                 @Override
-                public void logNotificationExpansion(String key, boolean userAction,
+                public void logNotificationExpansion(String key, int location, boolean userAction,
                         boolean expanded) {
-                    mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+                    mStatsLogger.onNotificationExpansionChanged(key, expanded, location,
+                            userAction);
                 }
 
                 @Override
@@ -212,7 +213,7 @@
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
             RowContentBindStage rowContentBindStage,
-            NotificationLogger notificationLogger,
+            NotificationRowStatsLogger statsLogger,
             HeadsUpManager headsUpManager,
             ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
             StatusBarStateController statusBarStateController,
@@ -239,7 +240,7 @@
         mGroupMembershipManager = groupMembershipManager;
         mGroupExpansionManager = groupExpansionManager;
         mRowContentBindStage = rowContentBindStage;
-        mNotificationLogger = notificationLogger;
+        mStatsLogger = statsLogger;
         mHeadsUpManager = headsUpManager;
         mOnExpandClickListener = onExpandClickListener;
         mStatusBarStateController = statusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index eb1c1ba..5527efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.shared
 
 import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 
 /**
  * Model for a top-level "entry" in the notification list, either an
@@ -55,6 +56,16 @@
     val shelfIcon: Icon?,
     /** Icon to display in the status bar. */
     val statusBarIcon: Icon?,
+    /** The notifying app's [packageName]'s uid. */
+    val uid: Int,
+    /** The notifying app's packageName. */
+    val packageName: String,
+    /** A small per-notification ID, used for statsd logging. */
+    val instanceId: Int?,
+    /** If this notification is the group summary for a group of notifications. */
+    val isGroupSummary: Boolean,
+    /** Indicates in which section the notification is displayed in. @see [PriorityBucket]. */
+    @PriorityBucket val bucket: Int,
 ) : ActiveNotificationEntryModel()
 
 /** Model for a group of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index 6bb9573..5c9a0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -23,7 +23,6 @@
 
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ea414d2..e791a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -113,6 +113,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -129,9 +130,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -245,6 +249,7 @@
      */
     private float mOverScrolledBottomPixels;
     private NotificationLogger.OnChildLocationsChangedListener mListener;
+    private OnNotificationLocationsChangedListener mLocationsChangedListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private Runnable mOnHeightChangedRunnable;
@@ -390,6 +395,14 @@
         }
     };
 
+    private final Callable<Map<String, Integer>> collectVisibleLocationsCallable =
+            new Callable<>() {
+                @Override
+                public Map<String, Integer> call() {
+                    return collectVisibleNotificationLocations();
+                }
+            };
+
     private boolean mPulsing;
     private boolean mScrollable;
     private View mForcedScroll;
@@ -1242,8 +1255,21 @@
         }
     }
 
+    /**
+     * @param listener to be notified after the location of Notification children might have
+     *                 changed.
+     */
+    public void setNotificationLocationsChangedListener(
+            @Nullable OnNotificationLocationsChangedListener listener) {
+        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        mLocationsChangedListener = listener;
+    }
+
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
         mListener = listener;
     }
 
@@ -4398,15 +4424,40 @@
             child.applyViewState();
         }
 
-        if (mListener != null) {
-            mListener.onChildLocationsChanged();
+        if (NotificationsLiveDataStoreRefactor.isEnabled()) {
+            if (mLocationsChangedListener != null) {
+                mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
+            }
+        } else {
+            if (mListener != null) {
+                mListener.onChildLocationsChanged();
+            }
         }
+
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
         updateBackground();
         updateViewShadows();
     }
 
+    /**
+     * Retrieves a map of visible [{@link ExpandableViewState#location}]s of the actively displayed
+     * Notification children associated by their Notification keys.
+     * Locations are collected recursively including locations from the child views of Notification
+     * Groups, that are visible.
+     */
+    private Map<String, Integer> collectVisibleNotificationLocations() {
+        Map<String, Integer> visibilities = new HashMap<>();
+        int numChildren = getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            ExpandableView child = getChildAtIndex(i);
+            if (child instanceof ExpandableNotificationRow row) {
+                row.collectVisibleLocations(visibilities);
+            }
+        }
+        return visibilities;
+    }
+
     private void updateViewShadows() {
         // we need to work around an issue where the shadow would not cast between siblings when
         // their z difference is between 0 and 0.1
@@ -5901,7 +5952,11 @@
         }
     }
 
-    protected void setLogger(StackStateLogger logger) {
+    /**
+     * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates
+     * the views.
+     */
+    protected void setStackStateLogger(StackStateLogger logger) {
         mStateAnimator.setLogger(logger);
     }
 
@@ -5938,6 +5993,18 @@
         void flingTopOverscroll(float velocity, boolean open);
     }
 
+    /**
+     * A listener that is notified when some ExpandableNotificationRow locations might have changed.
+     */
+    public interface OnNotificationLocationsChangedListener {
+        /**
+         * Called when the location of ExpandableNotificationRows might have changed.
+         *
+         * @param locations mapping of Notification keys to locations.
+         */
+        void onChildLocationsChanged(Callable<Map<String, Integer>> locations);
+    }
+
     private void updateSpeedBumpIndex() {
         mSpeedBumpIndexDirty = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a30c294..c0e0b73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -751,7 +751,7 @@
     }
 
     private void setUpView() {
-        mView.setLogger(mStackStateLogger);
+        mView.setStackStateLogger(mStackStateLogger);
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
index f9cdc1b..2305c7e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.model
+package com.android.systemui.statusbar.notification.stack.ui.view
 
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.sceneContainerConfig by
-    Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
+interface NotificationRowStatsLogger {
+    fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
new file mode 100644
index 0000000..5418616
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import java.util.concurrent.Callable
+
+/**
+ * Logs UI events of Notifications, in particular, logging which Notifications are visible and which
+ * are not.
+ */
+interface NotificationStatsLogger : NotificationRowStatsLogger {
+    fun onLockscreenOrShadeInteractive(
+        isOnLockScreen: Boolean,
+        activeNotifications: List<ActiveNotificationModel>,
+    )
+    fun onLockscreenOrShadeNotInteractive(activeNotifications: List<ActiveNotificationModel>)
+    fun onNotificationRemoved(key: String)
+    fun onNotificationUpdated(key: String)
+    fun onNotificationListUpdated(
+        locationsProvider: Callable<Map<String, Int>>,
+        notificationRanks: Map<String, Int>,
+    )
+    override fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
new file mode 100644
index 0000000..0cb00bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.NotificationListenerService
+import androidx.annotation.VisibleForTesting
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import java.util.concurrent.Callable
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@VisibleForTesting const val UNKNOWN_RANK = -1
+
+@SysUISingleton
+class NotificationStatsLoggerImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val notificationListenerService: NotificationListenerService,
+    private val notificationPanelLogger: NotificationPanelLogger,
+    private val statusBarService: IStatusBarService,
+) : NotificationStatsLogger {
+    private val lastLoggedVisibilities = mutableMapOf<String, VisibilityState>()
+    private var logVisibilitiesJob: Job? = null
+
+    private val expansionStates: MutableMap<String, ExpansionState> =
+        ConcurrentHashMap<String, ExpansionState>()
+    private val lastReportedExpansionValues: MutableMap<String, Boolean> =
+        ConcurrentHashMap<String, Boolean>()
+
+    override fun onNotificationListUpdated(
+        locationsProvider: Callable<Map<String, Int>>,
+        notificationRanks: Map<String, Int>,
+    ) {
+        if (logVisibilitiesJob?.isActive == true) {
+            return
+        }
+
+        logVisibilitiesJob =
+            startLogVisibilitiesJob(
+                newVisibilities =
+                    combine(
+                        visibilities = locationsProvider.call(),
+                        rankingsMap = notificationRanks
+                    ),
+                activeNotifCount = notificationRanks.size,
+            )
+    }
+
+    override fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean,
+    ) {
+        val expansionState =
+            ExpansionState(
+                key = key,
+                isExpanded = isExpanded,
+                isUserAction = isUserAction,
+                location = location,
+            )
+        expansionStates[key] = expansionState
+        maybeLogNotificationExpansionChange(expansionState)
+    }
+
+    private fun maybeLogNotificationExpansionChange(expansionState: ExpansionState) {
+        if (expansionState.visible.not()) {
+            // Only log visible expansion changes
+            return
+        }
+
+        val loggedExpansionValue: Boolean? = lastReportedExpansionValues[expansionState.key]
+        if (loggedExpansionValue == null && !expansionState.isExpanded) {
+            // Consider the Notification initially collapsed, so only expanded is logged in the
+            // first time.
+            return
+        }
+
+        if (loggedExpansionValue != null && loggedExpansionValue == expansionState.isExpanded) {
+            // We have already logged this state, don't log it again
+            return
+        }
+
+        logNotificationExpansionChange(expansionState)
+        lastReportedExpansionValues[expansionState.key] = expansionState.isExpanded
+    }
+
+    private fun logNotificationExpansionChange(expansionState: ExpansionState) =
+        applicationScope.launch {
+            withContext(bgDispatcher) {
+                statusBarService.onNotificationExpansionChanged(
+                    /* key = */ expansionState.key,
+                    /* userAction = */ expansionState.isUserAction,
+                    /* expanded = */ expansionState.isExpanded,
+                    /* notificationLocation = */ expansionState.location
+                        .toNotificationLocation()
+                        .ordinal
+                )
+            }
+        }
+
+    override fun onLockscreenOrShadeInteractive(
+        isOnLockScreen: Boolean,
+        activeNotifications: List<ActiveNotificationModel>,
+    ) {
+        applicationScope.launch {
+            withContext(bgDispatcher) {
+                notificationPanelLogger.logPanelShown(
+                    isOnLockScreen,
+                    activeNotifications.toNotificationProto()
+                )
+            }
+        }
+    }
+
+    override fun onLockscreenOrShadeNotInteractive(
+        activeNotifications: List<ActiveNotificationModel>
+    ) {
+        logVisibilitiesJob =
+            startLogVisibilitiesJob(
+                newVisibilities = emptyMap(),
+                activeNotifCount = activeNotifications.size
+            )
+    }
+
+    // TODO(b/308623704) wire this in with NotifPipeline updates
+    override fun onNotificationRemoved(key: String) {
+        // No need to track expansion states for Notifications that are removed.
+        expansionStates.remove(key)
+        lastReportedExpansionValues.remove(key)
+    }
+
+    // TODO(b/308623704) wire this in with NotifPipeline updates
+    override fun onNotificationUpdated(key: String) {
+        // When the Notification is updated, we should consider it as not yet logged.
+        lastReportedExpansionValues.remove(key)
+    }
+
+    private fun combine(
+        visibilities: Map<String, Int>,
+        rankingsMap: Map<String, Int>
+    ): Map<String, VisibilityState> =
+        visibilities.mapValues { entry ->
+            VisibilityState(entry.key, entry.value, rankingsMap[entry.key] ?: UNKNOWN_RANK)
+        }
+
+    private fun startLogVisibilitiesJob(
+        newVisibilities: Map<String, VisibilityState>,
+        activeNotifCount: Int,
+    ) =
+        applicationScope.launch {
+            val newlyVisible = newVisibilities - lastLoggedVisibilities.keys
+            val noLongerVisible = lastLoggedVisibilities - newVisibilities.keys
+
+            maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
+            updateExpansionStates(newlyVisible, noLongerVisible)
+
+            lastLoggedVisibilities.clear()
+            lastLoggedVisibilities.putAll(newVisibilities)
+        }
+
+    private suspend fun maybeLogVisibilityChanges(
+        newlyVisible: Map<String, VisibilityState>,
+        noLongerVisible: Map<String, VisibilityState>,
+        activeNotifCount: Int,
+    ) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return
+        }
+
+        val newlyVisibleAr =
+            newlyVisible.mapToNotificationVisibilitiesAr(visible = true, count = activeNotifCount)
+
+        val noLongerVisibleAr =
+            noLongerVisible.mapToNotificationVisibilitiesAr(
+                visible = false,
+                count = activeNotifCount
+            )
+
+        withContext(bgDispatcher) {
+            statusBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr)
+            if (newlyVisible.isNotEmpty()) {
+                notificationListenerService.setNotificationsShown(newlyVisible.keys.toTypedArray())
+            }
+        }
+    }
+
+    private fun updateExpansionStates(
+        newlyVisible: Map<String, VisibilityState>,
+        noLongerVisible: Map<String, VisibilityState>
+    ) {
+        expansionStates.forEach { (key, expansionState) ->
+            if (newlyVisible.contains(key)) {
+                val newState =
+                    expansionState.copy(
+                        visible = true,
+                        location = newlyVisible.getValue(key).location,
+                    )
+                expansionStates[key] = newState
+                maybeLogNotificationExpansionChange(newState)
+            }
+
+            if (noLongerVisible.contains(key)) {
+                expansionStates[key] =
+                    expansionState.copy(
+                        visible = false,
+                        location = noLongerVisible.getValue(key).location,
+                    )
+            }
+        }
+    }
+
+    private data class VisibilityState(
+        val key: String,
+        val location: Int,
+        val rank: Int,
+    )
+
+    private data class ExpansionState(
+        val key: String,
+        val isUserAction: Boolean,
+        val isExpanded: Boolean,
+        val visible: Boolean,
+        val location: Int,
+    ) {
+        constructor(
+            key: String,
+            isExpanded: Boolean,
+            location: Int,
+            isUserAction: Boolean,
+        ) : this(
+            key = key,
+            isExpanded = isExpanded,
+            isUserAction = isUserAction,
+            visible = isVisibleLocation(location),
+            location = location,
+        )
+    }
+
+    private fun Map<String, VisibilityState>.mapToNotificationVisibilitiesAr(
+        visible: Boolean,
+        count: Int,
+    ): Array<NotificationVisibility> =
+        this.map { (key, state) ->
+                NotificationVisibility.obtain(
+                    /* key = */ key,
+                    /* rank = */ state.rank,
+                    /* count = */ count,
+                    /* visible = */ visible,
+                    /* location = */ state.location.toNotificationLocation()
+                )
+            }
+            .toTypedArray()
+}
+
+private fun Int.toNotificationLocation(): NotificationVisibility.NotificationLocation {
+    return when (this) {
+        ExpandableViewState.LOCATION_FIRST_HUN ->
+            NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP
+        ExpandableViewState.LOCATION_HIDDEN_TOP ->
+            NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP
+        ExpandableViewState.LOCATION_MAIN_AREA ->
+            NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA
+        ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING ->
+            NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING
+        ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN ->
+            NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN
+        ExpandableViewState.LOCATION_GONE ->
+            NotificationVisibility.NotificationLocation.LOCATION_GONE
+        else -> NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN
+    }
+}
+
+private fun List<ActiveNotificationModel>.toNotificationProto(): Notifications.NotificationList {
+    val notificationList = Notifications.NotificationList()
+    val protoArray: Array<Notifications.Notification> =
+        map { notification ->
+                Notifications.Notification().apply {
+                    uid = notification.uid
+                    packageName = notification.packageName
+                    notification.instanceId?.let { instanceId = it }
+                    // TODO(b/308623704) check if we can set groupInstanceId as well
+                    isGroupSummary = notification.isGroupSummary
+                    section = NotificationPanelLogger.toNotificationSection(notification.bucket)
+                }
+            }
+            .toTypedArray()
+
+    if (protoArray.isNotEmpty()) {
+        notificationList.notifications = protoArray
+    }
+
+    return notificationList
+}
+
+private fun isVisibleLocation(location: Int): Boolean =
+    location and ExpandableViewState.VISIBLE_LOCATIONS != 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1b36660..da260cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,13 +33,16 @@
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.combine
@@ -49,13 +52,14 @@
 class NotificationListViewBinder
 @Inject
 constructor(
-    private val viewModel: NotificationListViewModel,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
     private val metricsLogger: MetricsLogger,
     private val nicBinder: NotificationIconContainerShelfViewBinder,
+    private val loggerOptional: Optional<NotificationStatsLogger>,
+    private val viewModel: NotificationListViewModel,
 ) {
 
     fun bindWhileAttached(
@@ -75,10 +79,15 @@
                 if (FooterViewRefactor.isEnabled) {
                     launch { bindFooter(view) }
                     launch { bindEmptyShade(view) }
-                    viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
-                        view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+                    launch {
+                        viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
+                            ->
+                            view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+                        }
                     }
                 }
+
+                launch { bindLogger(view) }
             }
         }
     }
@@ -136,4 +145,18 @@
                 )
             }
     }
+
+    private suspend fun bindLogger(view: NotificationStackScrollLayout) {
+        if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            viewModel.logger.getOrNull()?.let { viewModel ->
+                loggerOptional.getOrNull()?.let { logger ->
+                    NotificationStatsLoggerBinder.bindLogger(
+                        view,
+                        logger,
+                        viewModel,
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
new file mode 100644
index 0000000..a05ad6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.throttle
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Binds a [NotificationStatsLogger] to its [NotificationLoggerViewModel], and wires in
+ * [NotificationStackScrollLayout.OnNotificationLocationsChangedListener] updates to it.
+ */
+object NotificationStatsLoggerBinder {
+
+    /** minimum delay in ms between Notification location updates */
+    private const val NOTIFICATION_UPDATE_PERIOD_MS = 500L
+
+    suspend fun bindLogger(
+        view: NotificationStackScrollLayout,
+        logger: NotificationStatsLogger,
+        viewModel: NotificationLoggerViewModel,
+    ) {
+        viewModel.isLockscreenOrShadeInteractive
+            .sample(
+                combine(viewModel.isOnLockScreen, viewModel.activeNotifications, ::Pair),
+                Utils.Companion::toTriple
+            )
+            .collectLatest { (isPanelInteractive, isOnLockScreen, notifications) ->
+                if (isPanelInteractive) {
+                    logger.onLockscreenOrShadeInteractive(
+                        isOnLockScreen = isOnLockScreen,
+                        activeNotifications = notifications,
+                    )
+                    view.onNotificationsUpdated
+                        // Delay the updates with [NOTIFICATION_UPDATES_PERIOD_MS]. If the original
+                        // flow emits more than once during this period, only the latest value is
+                        // emitted, meaning that we won't log the intermediate Notification states.
+                        .throttle(NOTIFICATION_UPDATE_PERIOD_MS)
+                        .sample(viewModel.activeNotificationRanks, ::Pair)
+                        .collect { (locationsProvider, notificationRanks) ->
+                            logger.onNotificationListUpdated(locationsProvider, notificationRanks)
+                        }
+                } else {
+                    logger.onLockscreenOrShadeNotInteractive(
+                        activeNotifications = notifications,
+                    )
+                }
+            }
+    }
+}
+
+private val NotificationStackScrollLayout.onNotificationsUpdated
+    get() =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                NotificationStackScrollLayout.OnNotificationLocationsChangedListener { callable ->
+                    trySend(callable)
+                }
+            setNotificationLocationsChangedListener(callback)
+            awaitClose { setNotificationLocationsChangedListener(null) }
+        }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 569ae24..86c0a678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -40,6 +40,7 @@
     val shelf: NotificationShelfViewModel,
     val hideListViewModel: HideListViewModel,
     val footer: Optional<FooterViewModel>,
+    val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
new file mode 100644
index 0000000..0901a7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class NotificationLoggerViewModel
+@Inject
+constructor(
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+) {
+    val activeNotifications: Flow<List<ActiveNotificationModel>> =
+        activeNotificationsInteractor.allRepresentativeNotifications.map { it.values.toList() }
+
+    val activeNotificationRanks: Flow<Map<String, Int>> =
+        activeNotificationsInteractor.activeNotificationRanks
+
+    val isLockscreenOrShadeInteractive: Flow<Boolean> =
+        windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive
+
+    val isOnLockScreen: Flow<Boolean> = keyguardInteractor.isKeyguardShowing
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 3d7d701..d8ef981 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -175,7 +175,7 @@
                 mPrimaryBouncerInteractor,
                 mContext,
                 () -> mDeviceEntryInteractor,
-                mSceneTestUtils.getSceneContainerFlags()
+                mSceneTestUtils.getFakeSceneContainerFlags()
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 93a5393..132bdb5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@
     @Test
     public void longPress_showBouncer_sceneContainerNotEnabled() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(false);
+        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -387,7 +387,7 @@
     @Test
     public void longPress_showBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -401,7 +401,7 @@
     @Test
     public void longPress_falsingTriggered_doesNotShowBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
 
         // WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 647dae6..13306be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -44,6 +45,7 @@
     @Mock lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private lateinit var udfpsBpViewController: UdfpsBpViewController
 
@@ -55,7 +57,8 @@
                 statusBarStateController,
                 shadeInteractor,
                 systemUIDialogManager,
-                dumpManager
+                dumpManager,
+                udfpsOverlayInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 8f8004f..b1e471a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -5,6 +5,7 @@
 import android.hardware.biometrics.IBiometricContextListener.FoldState
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -17,6 +18,7 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
 import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,7 +44,10 @@
     private val testScope = TestScope()
 
     @Mock private lateinit var foldProvider: FoldStateProvider
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
 
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
 
     private lateinit var interactor: LogContextInteractorImpl
@@ -50,6 +55,13 @@
     @Before
     fun setup() {
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        udfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                context,
+                authController,
+                selectedUserInteractor,
+                testScope.backgroundScope,
+            )
         interactor =
             LogContextInteractorImpl(
                 testScope.backgroundScope,
@@ -59,6 +71,7 @@
                         scope = testScope.backgroundScope,
                     )
                     .keyguardTransitionInteractor,
+                udfpsOverlayInteractor,
             )
     }
 
@@ -162,6 +175,18 @@
         }
 
     @Test
+    fun isHardwareIgnoringTouchesChanges() =
+        testScope.runTest {
+            val isHardwareIgnoringTouches by collectLastValue(interactor.isHardwareIgnoringTouches)
+
+            udfpsOverlayInteractor.setHandleTouches(true)
+            assertThat(isHardwareIgnoringTouches).isFalse()
+
+            udfpsOverlayInteractor.setHandleTouches(false)
+            assertThat(isHardwareIgnoringTouches).isTrue()
+        }
+
+    @Test
     fun foldStateChanges() =
         testScope.runTest {
             val foldState = collectLastValue(interactor.foldState)
@@ -195,6 +220,7 @@
 
             var folded: Int? = null
             var displayState: Int? = null
+            var ignoreTouches: Boolean? = null
             val job =
                 interactor.addBiometricContextListener(
                     object : IBiometricContextListener.Stub() {
@@ -205,12 +231,17 @@
                         override fun onDisplayStateChanged(newDisplayState: Int) {
                             displayState = newDisplayState
                         }
+
+                        override fun onHardwareIgnoreTouchesChanged(newIgnoreTouches: Boolean) {
+                            ignoreTouches = newIgnoreTouches
+                        }
                     }
                 )
             runCurrent()
 
             assertThat(folded).isEqualTo(FoldState.FULLY_CLOSED)
             assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
+            assertThat(ignoreTouches).isFalse()
 
             foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
             foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
@@ -220,6 +251,11 @@
             assertThat(folded).isEqualTo(FoldState.HALF_OPENED)
             assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)
 
+            udfpsOverlayInteractor.setHandleTouches(false)
+            runCurrent()
+
+            assertThat(ignoreTouches).isTrue()
+
             job.cancel()
 
             // stale updates should be ignored
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 6a68672..c0e108e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -68,7 +68,7 @@
     @Test
     fun testShouldInterceptTouch() =
         testScope.runTest {
-            createUdpfsOverlayInteractor()
+            createUdfpsOverlayInteractor()
 
             // When fingerprint enrolled and touch is within bounds
             verify(authController).addCallback(authControllerCallback.capture())
@@ -92,7 +92,7 @@
     @Test
     fun testUdfpsOverlayParamsChange() =
         testScope.runTest {
-            createUdpfsOverlayInteractor()
+            createUdfpsOverlayInteractor()
             val udfpsOverlayParams = collectLastValue(underTest.udfpsOverlayParams)
             runCurrent()
 
@@ -105,7 +105,7 @@
             assertThat(udfpsOverlayParams()).isEqualTo(firstParams)
         }
 
-    private fun createUdpfsOverlayInteractor() {
+    private fun createUdfpsOverlayInteractor() {
         underTest =
             UdfpsOverlayInteractor(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index d5c3641..0dfdeca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -36,9 +36,9 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.testKosmos
-import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -72,8 +72,8 @@
 
             // It's been 10 seconds since the last power button wakeup
             setAwakeFromPowerButton()
+            advanceTimeBy(10000)
             runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
             enterDeviceFromBiometricUnlock()
             assertThat(playSuccessHaptic).isNotNull()
@@ -89,8 +89,8 @@
 
             // It's been 10 seconds since the last power button wakeup
             setAwakeFromPowerButton()
+            advanceTimeBy(10000)
             runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
             enterDeviceFromBiometricUnlock()
             assertThat(playSuccessHaptic).isNull()
@@ -106,8 +106,8 @@
 
             // It's only been 50ms since the last power button wakeup
             setAwakeFromPowerButton()
+            advanceTimeBy(50)
             runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
 
             enterDeviceFromBiometricUnlock()
             assertThat(playSuccessHaptic).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 9941661..543f6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -16,17 +16,13 @@
 
 package com.android.systemui.scene.shared.flag
 
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.setFlagValue
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.flags.EnableSceneContainer
 import com.google.common.truth.Truth
-import org.junit.Assume
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -34,45 +30,17 @@
 @RunWith(AndroidJUnit4::class)
 internal class SceneContainerFlagsTest : SysuiTestCase() {
 
-    @Before
-    fun setUp() {
-        // TODO(b/283300105): remove this reflection setting once the hard-coded
-        //  Flags.SCENE_CONTAINER_ENABLED is no longer needed.
-        val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
-        field.isAccessible = true
-        field.set(null, true) // note: this does not work with multivalent tests
-    }
-
-    private fun setAconfigFlagsEnabled(enabled: Boolean) {
-        listOf(
-                com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
-                com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
-                KeyguardShadeMigrationNssl.FLAG_NAME,
-                MediaInSceneContainerFlag.FLAG_NAME,
-            )
-            .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
-    }
-
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     fun isNotEnabled_withoutAconfigFlags() {
-        setAconfigFlagsEnabled(false)
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
         Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
     }
 
     @Test
-    fun isEnabled_withAconfigFlags_withCompose() {
-        Assume.assumeTrue(ComposeFacade.isComposeAvailable())
-        setAconfigFlagsEnabled(true)
+    @EnableSceneContainer
+    fun isEnabled_withAconfigFlags() {
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
         Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
     }
-
-    @Test
-    fun isNotEnabled_withAconfigFlags_withoutCompose() {
-        Assume.assumeFalse(ComposeFacade.isComposeAvailable())
-        setAconfigFlagsEnabled(true)
-        Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
-        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
-    }
 }
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 791c080..971c8a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -131,7 +131,6 @@
     @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
             new NotificationShadeWindowView(mContext, null));
     @Mock private IActivityManager mActivityManager;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private KeyguardBypassController mKeyguardBypassController;
@@ -140,7 +139,6 @@
     @Mock private DumpManager mDumpManager;
     @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
     @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private AuthController mAuthController;
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
@@ -160,6 +158,8 @@
     private float mPreferredRefreshRate = -1;
     private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
     private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+    private ScreenOffAnimationController mScreenOffAnimationController;
+    private SysuiStatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
@@ -178,11 +178,9 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeShadeRepository shadeRepository = new FakeShadeRepository();
 
-        PowerInteractor powerInteractor = mUtils.powerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
-                mScreenOffAnimationController,
-                mStatusBarStateController);
+        mScreenOffAnimationController = mUtils.getScreenOffAnimationController();
+        mStatusBarStateController = spy(mUtils.getStatusBarStateController());
+        PowerInteractor powerInteractor = mUtils.powerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index ea3caa3..697b05a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -167,10 +168,15 @@
     }
 
     @Test
-    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
+        val headerResourceHeight = 20
+        val headerHelperHeight = 30
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
         overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
 
         // ensure the estimated height (would be 3 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -180,7 +186,31 @@
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+            .isEqualTo(headerResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+        val headerResourceHeight = 20
+        val headerHelperHeight = 30
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(headerHelperHeight)
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+            .isEqualTo(headerHelperHeight)
     }
 
     @Test
@@ -416,10 +446,14 @@
     }
 
     @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
         setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+        val largeScreenHeaderResourceHeight = 100
+        val largeScreenHeaderHelperHeight = 200
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
 
         // ensure the estimated height (would be 30 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -428,9 +462,31 @@
         underTest.updateResources()
 
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        setLargeScreen()
+        val largeScreenHeaderResourceHeight = 100
+        val largeScreenHeaderHelperHeight = 200
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index c1bc303..e66251a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -166,10 +167,14 @@
     }
 
     @Test
-    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        val helperHeight = 30
+        val resourceHeight = 20
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
         overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
 
         // ensure the estimated height (would be 3 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -179,7 +184,28 @@
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        val helperHeight = 30
+        val resourceHeight = 20
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(helperHeight)
     }
 
     @Test
@@ -404,10 +430,14 @@
     }
 
     @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
         setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+        val largeScreenHeaderHelperHeight = 200
+        val largeScreenHeaderResourceHeight = 100
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
 
         // ensure the estimated height (would be 30 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -416,7 +446,27 @@
         underTest.updateResources()
 
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        setLargeScreen()
+        val largeScreenHeaderHelperHeight = 200
+        val largeScreenHeaderResourceHeight = 100
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
     }
 
     @Test
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 a369f82..ca68fd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -85,7 +85,6 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
@@ -99,7 +98,6 @@
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
@@ -172,7 +170,6 @@
     @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
     @Mock protected MetricsLogger mMetricsLogger;
     @Mock protected FeatureFlags mFeatureFlags;
-    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ShadeLogger mShadeLogger;
     @Mock protected DumpManager mDumpManager;
     @Mock protected UiEventLogger mUiEventLogger;
@@ -186,6 +183,7 @@
     protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     protected FakeShadeRepository mShadeRepository = new FakeShadeRepository();
 
+    protected InteractionJankMonitor mInteractionJankMonitor;
     protected SysuiStatusBarStateController mStatusBarStateController;
     protected ShadeInteractor mShadeInteractor;
 
@@ -205,8 +203,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
-                mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor);
+        mStatusBarStateController = mUtils.getStatusBarStateController();
+        mInteractionJankMonitor = mUtils.getInteractionJankMonitor();
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
                 new FakeDeviceProvisioningRepository();
@@ -214,11 +212,7 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
-        PowerInteractor powerInteractor = mUtils.powerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
-                mock(ScreenOffAnimationController.class),
-                mStatusBarStateController);
+        PowerInteractor powerInteractor = mUtils.powerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 4ab3cd4..b3b10eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -74,6 +74,18 @@
         }
 
     @Test
+    fun testActiveNotificationRanks_sizeMatches() {
+        testComponent.runTest {
+            val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)
+
+            activeNotificationListRepository.setActiveNotifs(5)
+            runCurrent()
+
+            assertThat(activeNotificationRanks!!.size).isEqualTo(5)
+        }
+    }
+
+    @Test
     fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
         testComponent.runTest {
             val hasClearable by collectLastValue(underTest.hasClearableNotifications)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 6374d5e..334776c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -15,10 +15,12 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.byKey
@@ -49,22 +51,101 @@
         testScope.runTest {
             val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
             val keys = (1..50).shuffled().map { "$it" }
-            val entries =
-                keys.map {
-                    mock<ListEntry> {
-                        val mockRep =
-                            mock<NotificationEntry> {
-                                whenever(key).thenReturn(it)
-                                whenever(sbn).thenReturn(mock())
-                                whenever(icons).thenReturn(mock())
-                            }
-                        whenever(representativeEntry).thenReturn(mockRep)
-                    }
-                }
+            val entries = keys.map { mockNotificationEntry(key = it) }
             underTest.setRenderedList(entries)
             assertThat(notifs)
                 .comparingElementsUsing(byKey)
                 .containsExactlyElementsIn(keys)
                 .inOrder()
         }
+
+    @Test
+    fun setRenderList_flatMapsRankings() =
+        testScope.runTest {
+            val ranks by collectLastValue(notifsInteractor.activeNotificationRanks)
+
+            val single = mockNotificationEntry("single", 0)
+            val group =
+                mockGroupEntry(
+                    key = "group",
+                    summary = mockNotificationEntry("summary", 1),
+                    children =
+                        listOf(
+                            mockNotificationEntry("child0", 2),
+                            mockNotificationEntry("child1", 3),
+                        ),
+                )
+
+            underTest.setRenderedList(listOf(single, group))
+
+            assertThat(ranks)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        "single" to 0,
+                        "summary" to 1,
+                        "child0" to 2,
+                        "child1" to 3,
+                    )
+                )
+        }
+
+    @Test
+    fun setRenderList_singleItems_mapsRankings() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+            val expected =
+                (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+            val entries = expected.map { (key, rank) -> mockNotificationEntry(key, rank) }
+
+            underTest.setRenderedList(entries)
+
+            assertThat(actual).containsAtLeastEntriesIn(expected)
+        }
+
+    @Test
+    fun setRenderList_groupWithNoSummary_flatMapsRankings() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+            val expected =
+                (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+            val group =
+                mockGroupEntry(
+                    key = "group",
+                    summary = null,
+                    children = expected.map { (key, rank) -> mockNotificationEntry(key, rank) },
+                )
+
+            underTest.setRenderedList(listOf(group))
+
+            assertThat(actual).containsAtLeastEntriesIn(expected)
+        }
+}
+
+private fun mockGroupEntry(
+    key: String,
+    summary: NotificationEntry?,
+    children: List<NotificationEntry>,
+): GroupEntry {
+    return mock<GroupEntry> {
+        whenever(this.key).thenReturn(key)
+        whenever(this.summary).thenReturn(summary)
+        whenever(this.children).thenReturn(children)
+    }
+}
+
+private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
+    val mockSbn =
+        mock<StatusBarNotification>() {
+            whenever(notification).thenReturn(mock())
+            whenever(packageName).thenReturn("com.android")
+        }
+    return mock<NotificationEntry> {
+        whenever(this.key).thenReturn(key)
+        whenever(this.icons).thenReturn(mock())
+        whenever(this.representativeEntry).thenReturn(this)
+        whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+        whenever(this.sbn).thenReturn(mockSbn)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index dae0aa2..d61fc05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -34,6 +34,11 @@
     }
 
     @Override
+    public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+        mCalls.add(new CallRecord(isLockscreen, proto));
+    }
+
+    @Override
     public void logPanelShown(boolean isLockscreen,
             List<NotificationEntry> visibleNotifications) {
         mCalls.add(new CallRecord(isLockscreen,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 9547af1..8ac2a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -40,12 +40,12 @@
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartReplyConstants
@@ -92,7 +92,7 @@
     private val groupMembershipManager: GroupMembershipManager = mock()
     private val groupExpansionManager: GroupExpansionManager = mock()
     private val rowContentBindStage: RowContentBindStage = mock()
-    private val notifLogger: NotificationLogger = mock()
+    private val notifLogger: NotificationRowStatsLogger = mock()
     private val headsUpManager: HeadsUpManager = mock()
     private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
     private val statusBarStateController: StatusBarStateController = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
new file mode 100644
index 0000000..d7d1ffc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Callable
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStatsLoggerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val mockNotificationListenerService = kosmos.notificationListenerService
+    private val mockPanelLogger = kosmos.notificationPanelLogger
+    private val mockStatusBarService = kosmos.statusBarService
+
+    private val underTest = kosmos.notificationStatsLogger
+
+    private val visibilityArrayCaptor = argumentCaptor<Array<NotificationVisibility>>()
+    private val stringArrayCaptor = argumentCaptor<Array<String>>()
+    private val notificationListProtoCaptor = argumentCaptor<Notifications.NotificationList>()
+
+    @Test
+    fun onNotificationListUpdated_itemsAdded_logsNewlyVisibleItems() =
+        testScope.runTest {
+            // WHEN new Notifications are added
+            // AND they're visible
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(visibilityArrayCaptor.capture(), eq(emptyArray()))
+            verify(mockNotificationListenerService)
+                .setNotificationsShown(stringArrayCaptor.capture())
+            val loggedVisibilities = visibilityArrayCaptor.value
+            val loggedKeys = stringArrayCaptor.value
+            assertThat(loggedVisibilities).hasLength(2)
+            assertThat(loggedKeys).hasLength(2)
+            assertThat(loggedVisibilities[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                isVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(loggedVisibilities[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                isVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(loggedKeys[0]).isEqualTo("key0")
+            assertThat(loggedKeys[1]).isEqualTo("key1")
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsRemoved_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the same Notifications are removed
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationListUpdated(emptyCallable, emptyMap())
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsBecomeInvisible_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the same Notifications are becoming invisible
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationListUpdated(emptyCallable, ranks)
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsChangedPositions_nothingLogged() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            underTest.onNotificationListUpdated({ locations }, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the reported Notifications are changing positions
+            val (newRanks, newLocations) = fakeNotificationMaps("key1", "key0")
+            underTest.onNotificationListUpdated({ newLocations }, newRanks)
+            runCurrent()
+
+            // THEN no visibility changes are reported
+            verifyZeroInteractions(mockStatusBarService, mockNotificationListenerService)
+        }
+
+    @Test
+    fun onNotificationListUpdated_calledTwice_usesTheNewCallable() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1", "key2")
+            val callable = spy(Callable { locations })
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+            clearInvocations(callable)
+
+            // WHEN a new update comes
+            val otherCallable = spy(Callable { locations })
+            underTest.onNotificationListUpdated(otherCallable, ranks)
+            runCurrent()
+
+            // THEN we call the new Callable
+            verifyZeroInteractions(callable)
+            verify(otherCallable).call()
+        }
+
+    @Test
+    fun onLockscreenOrShadeNotInteractive_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the Shade becomes non interactive
+            underTest.onLockscreenOrShadeNotInteractive(emptyList())
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+        }
+
+    @Test
+    fun onLockscreenOrShadeInteractive_logsPanelShown() =
+        testScope.runTest {
+            // WHEN the Shade becomes interactive
+            underTest.onLockscreenOrShadeInteractive(
+                isOnLockScreen = true,
+                listOf(
+                    activeNotificationModel(
+                        key = "key0",
+                        uid = 0,
+                        packageName = "com.android.first"
+                    ),
+                    activeNotificationModel(
+                        key = "key1",
+                        uid = 1,
+                        packageName = "com.android.second"
+                    ),
+                )
+            )
+            runCurrent()
+
+            // THEN the Panel shown event is reported
+            verify(mockPanelLogger).logPanelShown(eq(true), notificationListProtoCaptor.capture())
+            val loggedNotifications = notificationListProtoCaptor.value.notifications
+            assertThat(loggedNotifications.size).isEqualTo(2)
+            with(loggedNotifications[0]) {
+                assertThat(uid).isEqualTo(0)
+                assertThat(packageName).isEqualTo("com.android.first")
+            }
+            with(loggedNotifications[1]) {
+                assertThat(uid).isEqualTo(1)
+                assertThat(packageName).isEqualTo("com.android.second")
+            }
+        }
+
+    @Test
+    fun onNotificationExpanded_visibleLocation_expansionLogged() =
+        testScope.runTest {
+            // WHEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the Expand event is reported
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationExpanded_notVisibleLocation_nothingLogged() =
+        testScope.runTest {
+            // WHEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // No events are reported
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpanded_notVisibleLocation_becomesVisible_expansionLogged() =
+        testScope.runTest {
+            // WHEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_GONE,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // AND it becomes visible
+            val (ranks, locations) = fakeNotificationMaps("key")
+            val callable = Callable { locations }
+            underTest.onNotificationListUpdated(callable, ranks)
+            runCurrent()
+
+            // THEN the Expand event is reported
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationCollapsed_isFirstInteraction_nothingLogged() =
+        testScope.runTest {
+            // WHEN a Notification is collapsed, and it is the first interaction
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = false,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = false
+            )
+            runCurrent()
+
+            // THEN no events are reported, because we consider the Notification initially
+            // collapsed, so only expanded is logged in the first time.
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpandedAndCollapsed_expansionChangesLogged() =
+        testScope.runTest {
+            // GIVEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // WHEN the Notification is collapsed
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+
+            // AND the Notification is expanded again
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = false,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the expansion changes are logged
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ false,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    private fun fakeNotificationMaps(
+        vararg keys: String
+    ): Pair<Map<String, Int>, Map<String, Int>> {
+        val ranks: Map<String, Int> = keys.mapIndexed { index, key -> key to index }.toMap()
+        val locations: Map<String, Int> =
+            keys.associateWith { ExpandableViewState.LOCATION_MAIN_AREA }
+
+        return Pair(ranks, locations)
+    }
+
+    private fun assertThat(visibility: NotificationVisibility) =
+        NotificationVisibilitySubject(visibility)
+}
+
+private class NotificationVisibilitySubject(private val visibility: NotificationVisibility) {
+    fun isKeyEqualTo(key: String) = assertThat(visibility.key).isEqualTo(key)
+    fun isRankEqualTo(rank: Int) = assertThat(visibility.rank).isEqualTo(rank)
+    fun isCountEqualTo(count: Int) = assertThat(visibility.count).isEqualTo(count)
+    fun isVisible() = assertThat(this.visibility.visible).isTrue()
+    fun notVisible() = assertThat(this.visibility.visible).isFalse()
+    fun isInMainArea() =
+        assertThat(this.visibility.location)
+            .isEqualTo(NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index c17a8ef..4188c5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -71,6 +72,7 @@
                 FooterViewModelModule::class,
                 HeadlessSystemUserModeModule::class,
                 UnfoldTransitionModule.Bindings::class,
+                NotificationStatsLoggerModule::class,
             ]
     )
     interface TestComponent : SysUITestComponent<NotificationListViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
new file mode 100644
index 0000000..e9d88cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationLoggerViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerInteractor = kosmos.powerInteractor
+    private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
+
+    private val underTest = kosmos.notificationListLoggerViewModel
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
+        testScope.runTest {
+            powerInteractor.setAsleepForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun activeNotifications_hasNotifications() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(5)
+
+            val notifs by collectLastValue(underTest.activeNotifications)
+
+            assertThat(notifs).hasSize(5)
+            requireNotNull(notifs).forEachIndexed { i, notif ->
+                assertThat(notif.key).isEqualTo("$i")
+            }
+        }
+
+    @Test
+    fun activeNotifications_isEmpty() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(0)
+
+            val notifications by collectLastValue(underTest.activeNotifications)
+
+            assertThat(notifications).isEmpty()
+        }
+
+    @Test
+    fun activeNotificationRanks_hasNotifications() =
+        testScope.runTest {
+            val keys = (0..4).map { "$it" }
+            activeNotificationListRepository.setActiveNotifs(5)
+
+            val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+            assertThat(rankingsMap).hasSize(5)
+            keys.forEachIndexed { rank, key -> assertThat(rankingsMap).containsEntry(key, rank) }
+        }
+
+    @Test
+    fun activeNotificationRanks_isEmpty() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(0)
+
+            val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+            assertThat(rankingsMap).isEmpty()
+        }
+
+    @Test
+    fun isOnLockScreen_true() =
+        testScope.runTest {
+            keyguardRepository.setKeyguardShowing(true)
+
+            val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+            assertThat(isOnLockScreen).isTrue()
+        }
+    @Test
+    fun isOnLockScreen_false() =
+        testScope.runTest {
+            keyguardRepository.setKeyguardShowing(false)
+
+            val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+            assertThat(isOnLockScreen).isFalse()
+        }
+}
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 20020f2..9f15b05 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
@@ -21,6 +21,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -39,8 +40,11 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -66,6 +70,7 @@
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     val shadeRepository = kosmos.shadeRepository
     val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
+    val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
 
     val underTest = kosmos.sharedNotificationContainerViewModel
 
@@ -101,8 +106,10 @@
         }
 
     @Test
-    fun validatePaddingTopInSplitShade() =
+    fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -115,6 +122,22 @@
         }
 
     @Test
+    fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, 10)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.paddingTop).isEqualTo(40)
+        }
+
+    @Test
     fun validatePaddingTop() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
@@ -153,17 +176,41 @@
         }
 
     @Test
-    fun validateMarginTopWithLargeScreenHeader() =
+    fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
-            overrideResource(R.dimen.large_screen_shade_header_height, 50)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
             overrideResource(R.dimen.notification_panel_margin_top, 0)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
 
             configurationRepository.onAnyConfigurationChange()
 
-            assertThat(dimens!!.marginTop).isEqualTo(50)
+            assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight)
+        }
+
+    @Test
+    fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
+            overrideResource(R.bool.config_use_large_screen_shade_header, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+            overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.marginTop).isEqualTo(headerHelperHeight)
         }
 
     @Test
@@ -275,11 +322,13 @@
         }
 
     @Test
-    fun boundsOnLockscreenInSplitShade() =
+    fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
             val bounds by collectLastValue(underTest.bounds)
 
             // When in split shade
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -300,6 +349,33 @@
         }
 
     @Test
+    fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val bounds by collectLastValue(underTest.bounds)
+
+            // When in split shade
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, 10)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
+
+            // Start on lockscreen
+            showLockscreen()
+
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
+            runCurrent()
+
+            // Top should be equal to bounds (1) + padding adjustment (40)
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 41f, bottom = 2f))
+        }
+
+    @Test
     fun boundsOnShade() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 4dc4798..bbf9a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,11 +30,13 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -79,6 +81,7 @@
         MockitoAnnotations.initMocks(this);
         mStaticMockSession = mockitoSession()
                 .mockStatic(BurnInHelperKt.class)
+                .mockStatic(LargeScreenHeaderHelper.class)
                 .startMocking();
 
         LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
@@ -292,18 +295,44 @@
     }
 
     @Test
-    public void notifPaddingMakesUpToFullMarginInSplitShade() {
+    public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+        int keyguardSplitShadeTopMargin = 100;
+        int largeScreenHeaderHeightResource = 70;
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
-                .thenReturn(100);
+                .thenReturn(keyguardSplitShadeTopMargin);
         when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
-                .thenReturn(70);
+                .thenReturn(largeScreenHeaderHeightResource);
         mClockPositionAlgorithm.loadDimens(mContext, mResources);
         givenLockScreen();
         mIsSplitShade = true;
         // WHEN the position algorithm is run
         positionClock();
-        // THEN the notif padding makes up lacking margin (margin - header height = 30).
-        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30);
+        // THEN the notif padding makes up lacking margin (margin - header height).
+        int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightResource;
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
+    }
+
+    @Test
+    public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+        int keyguardSplitShadeTopMargin = 100;
+        int largeScreenHeaderHeightHelper = 50;
+        int largeScreenHeaderHeightResource = 70;
+        when(LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext))
+                .thenReturn(largeScreenHeaderHeightHelper);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
+                .thenReturn(keyguardSplitShadeTopMargin);
+        when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
+                .thenReturn(largeScreenHeaderHeightResource);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
+        givenLockScreen();
+        mIsSplitShade = true;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the notif padding makes up lacking margin (margin - header height).
+        int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightHelper;
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 5102b4f..5fa7f13e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -166,7 +166,7 @@
                 mKeyguardRepository,
                 mCommandQueue,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mSceneTestUtils.getSceneContainerFlags(),
+                mSceneTestUtils.getFakeSceneContainerFlags(),
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(new FakeConfigurationRepository()),
                 new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 9419d63..5f9c096 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -55,7 +55,7 @@
             keyguardRepository,
             mock<CommandQueue>(),
             PowerInteractorFactory.create().powerInteractor,
-            sceneTestUtils.sceneContainerFlags,
+            sceneTestUtils.fakeSceneContainerFlags,
             FakeKeyguardBouncerRepository(),
             ConfigurationInteractor(FakeConfigurationRepository()),
             FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index fb5375a..e6b9d9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -121,10 +121,10 @@
             SUPERVISED_USER_CREATION_APP_PACKAGE,
         )
 
-        utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
         spyContext = spy(context)
-        keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
+        keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags)
         keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
         refreshUsersScheduler =
@@ -363,7 +363,7 @@
     fun actions_deviceUnlocked_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
             userRepository.setUserInfos(userInfos)
@@ -447,7 +447,7 @@
     fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
@@ -792,7 +792,7 @@
     fun userRecordsFullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
             userRepository.setUserInfos(userInfos)
@@ -901,7 +901,7 @@
     fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -1139,7 +1139,7 @@
                         resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     ),
                 uiEventLogger = uiEventLogger,
-                featureFlags = utils.featureFlags,
+                featureFlags = utils.fakeFeatureFlags,
                 userRestrictionChecker = mock(),
             )
     }
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index f96c508..5e254bf 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -16,9 +16,7 @@
 
 package android.content
 
-import com.android.systemui.SysuiTestableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 
-val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context }
-var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
similarity index 77%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
rename to packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
index d98f496..bff0d0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package android.service.notification
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationListenerService by Fixture { mockNotificationListenerService }
+val Kosmos.mockNotificationListenerService by Fixture { mock<NotificationListenerService>() }
diff --git a/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
new file mode 100644
index 0000000..a4ee702
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+val Kosmos.telephonyManager by Fixture {
+    mock<TelephonyManager> {
+        whenever(createForSubscriptionId(anyInt())).thenReturn(this)
+        whenever(supplyIccLockPin(anyString()))
+            .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 3))
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
index d98f496..fa3e8f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.app
 
+import android.app.ActivityTaskManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.activityTaskManager by Fixture { mock<ActivityTaskManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
index a1815c5..eedc0b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
@@ -16,8 +16,9 @@
 
 package com.android.internal.logging
 
+import com.android.internal.logging.testing.FakeMetricsLogger
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.metricsLogger by Fixture { mock<MetricsLogger>() }
+val Kosmos.fakeMetricsLogger by Fixture { FakeMetricsLogger() }
+val Kosmos.metricsLogger by Fixture<MetricsLogger> { fakeMetricsLogger }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
index d98f496..3133437 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.internal.util
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.emergencyAffordanceManager by Fixture { mock<EmergencyAffordanceManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index d23dae9..af7f4c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -39,6 +39,7 @@
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
+import com.android.systemui.flags.SceneContainerRule;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -68,6 +69,9 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
+    @Rule(order = 10)
+    public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
+
     @Rule
     public SysuiTestableContext mContext = createTestableContext();
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index fdb9b30..b18859d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -18,9 +18,11 @@
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
 import android.os.UserManager
+import android.service.notification.NotificationListenerService
 import android.util.DisplayMetrics
 import android.view.LayoutInflater
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardViewController
@@ -49,9 +51,11 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
@@ -59,6 +63,7 @@
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -80,6 +85,7 @@
     @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
     @get:Provides val dozeParameters: DozeParameters = mock(),
     @get:Provides val dumpManager: DumpManager = mock(),
+    @get:Provides val headsUpManager: HeadsUpManager = mock(),
     @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
     @get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
     @get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@@ -89,8 +95,10 @@
     val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock(),
     @get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
     @get:Provides val notifCollection: NotifCollection = mock(),
+    @get:Provides val notificationListLogger: NotificationStatsLogger = mock(),
     @get:Provides val notificationListener: NotificationListener = mock(),
     @get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
+    @get:Provides val notificationPanelLogger: NotificationPanelLogger = mock(),
     @get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
     @get:Provides val notificationShadeDepthController: NotificationShadeDepthController = mock(),
     @get:Provides
@@ -129,6 +137,10 @@
     @get:Provides val displayMetrics: DisplayMetrics = mock(),
     @get:Provides val metricsLogger: MetricsLogger = mock(),
     @get:Provides val userManager: UserManager = mock(),
+
+    // system server mocks
+    @get:Provides val mockStatusBarService: IStatusBarService = mock(),
+    @get:Provides val mockNotificationListenerService: NotificationListenerService = mock(),
 ) {
     @Module
     interface Bindings {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
index d98f496..c0f8638 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.bouncer.data.repository
 
+import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.bouncerRepository by Fixture {
+    BouncerRepository(
+        flags = featureFlagsClassic,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
new file mode 100644
index 0000000..8851709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import android.content.res.mainResources
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.emergencyServicesRepository by Fixture {
+    EmergencyServicesRepository(
+        applicationScope = testScope.backgroundScope,
+        resources = mainResources,
+        configurationRepository = configurationRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
index d98f496..7af39df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.bouncer.data.repository
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeSimBouncerRepository by Fixture { FakeSimBouncerRepository() }
+
+val Kosmos.simBouncerRepository by Fixture<SimBouncerRepository> { fakeSimBouncerRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index 86a4509..c4fc30d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
 
 var Kosmos.alternateBouncerInteractor by
     Kosmos.Fixture {
@@ -35,7 +35,7 @@
             bouncerRepository = keyguardBouncerRepository,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
             biometricSettingsRepository = biometricSettingsRepository,
-            systemClock = fakeSystemClock,
+            systemClock = systemClock,
             keyguardUpdateMonitor = keyguardUpdateMonitor,
             scope = testScope.backgroundScope,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
new file mode 100644
index 0000000..5ced578
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.Intent
+import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.internal.logging.metricsLogger
+import com.android.internal.util.emergencyAffordanceManager
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.emergencyServicesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+import com.android.telecom.telecomManager
+
+val Kosmos.bouncerActionButtonInteractor by Fixture {
+    BouncerActionButtonInteractor(
+        applicationContext = applicationContext,
+        backgroundDispatcher = testDispatcher,
+        repository = emergencyServicesRepository,
+        mobileConnectionsRepository = mobileConnectionsRepository,
+        telephonyInteractor = telephonyInteractor,
+        authenticationInteractor = authenticationInteractor,
+        selectedUserInteractor = selectedUserInteractor,
+        activityTaskManager = activityTaskManager,
+        telecomManager = telecomManager,
+        emergencyAffordanceManager = emergencyAffordanceManager,
+        emergencyDialerIntentFactory =
+            object : EmergencyDialerIntentFactory {
+                override fun invoke(): Intent = Intent()
+            },
+        metricsLogger = metricsLogger,
+        dozeLogger = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
new file mode 100644
index 0000000..27803b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.bouncerInteractor by Fixture {
+    BouncerInteractor(
+        applicationScope = testScope.backgroundScope,
+        applicationContext = applicationContext,
+        repository = bouncerRepository,
+        authenticationInteractor = authenticationInteractor,
+        deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+        falsingInteractor = falsingInteractor,
+        powerInteractor = powerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..8ed9f45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.telephony.euicc.EuiccManager
+import android.telephony.telephonyManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.simBouncerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+
+val Kosmos.simBouncerInteractor by Fixture {
+    SimBouncerInteractor(
+        applicationContext = applicationContext,
+        backgroundDispatcher = testDispatcher,
+        applicationScope = testScope.backgroundScope,
+        repository = simBouncerRepository,
+        telephonyManager = telephonyManager,
+        resources = mainResources,
+        keyguardUpdateMonitor = keyguardUpdateMonitor,
+        euiccManager = applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
+        mobileConnectionsRepository = mobileConnectionsRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
new file mode 100644
index 0000000..d91c597
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.bouncerViewModel by Fixture {
+    BouncerViewModel(
+        applicationContext = applicationContext,
+        applicationScope = testScope.backgroundScope,
+        mainDispatcher = testDispatcher,
+        bouncerInteractor = bouncerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        flags = sceneContainerFlags,
+        selectedUser = userSwitcherViewModel.selectedUser,
+        users = userSwitcherViewModel.users,
+        userSwitcherMenu = userSwitcherViewModel.menu,
+        actionButton = bouncerActionButtonInteractor.actionButton,
+        clock = systemClock,
+        devicePolicyManager = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
index d98f496..8fee5b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.classifier.domain.interactor
 
+import com.android.systemui.classifier.falsingCollector
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.falsingInteractor by Fixture {
+    FalsingInteractor(
+        collector = falsingCollector,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
index 7946446..0768106 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.communalMediaRepository: CommunalMediaRepository by
-    Kosmos.Fixture { fakeCommunalMediaRepository }
 val Kosmos.fakeCommunalMediaRepository by Kosmos.Fixture { FakeCommunalMediaRepository() }
+
+val Kosmos.communalMediaRepository by
+    Kosmos.Fixture<CommunalMediaRepository> { fakeCommunalMediaRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
index be56d2b..1f5af5c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-var Kosmos.communalRepository: CommunalRepository by Kosmos.Fixture { fakeCommunalRepository }
-val Kosmos.fakeCommunalRepository by Kosmos.Fixture { FakeCommunalRepository() }
+val Kosmos.fakeCommunalRepository by Fixture { FakeCommunalRepository() }
+
+val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
index 5a17f2f8..c225e3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
@@ -17,9 +17,14 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 
-var Kosmos.communalWidgetRepository: CommunalWidgetRepository by
-    Kosmos.Fixture { fakeCommunalWidgetRepository }
-val Kosmos.fakeCommunalWidgetRepository by
-    Kosmos.Fixture { FakeCommunalWidgetRepository(applicationCoroutineScope) }
+val Kosmos.fakeCommunalWidgetRepository by Fixture {
+    FakeCommunalWidgetRepository(
+        coroutineScope = applicationCoroutineScope,
+    )
+}
+
+val Kosmos.communalWidgetRepository by
+    Fixture<CommunalWidgetRepository> { fakeCommunalWidgetRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
new file mode 100644
index 0000000..649b373
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.communalInteractor by Fixture {
+    CommunalInteractor(
+        communalRepository = communalRepository,
+        widgetRepository = communalWidgetRepository,
+        mediaRepository = communalMediaRepository,
+        smartspaceRepository = smartspaceRepository,
+        appWidgetHost = mock(),
+        keyguardInteractor = keyguardInteractor,
+        editWidgetsActivityStarter = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 6bf527d..de58ae5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.deviceEntryHapticsInteractor by
@@ -37,7 +37,7 @@
             biometricSettingsRepository = biometricSettingsRepository,
             keyEventInteractor = keyEventInteractor,
             powerInteractor = powerInteractor,
-            systemClock = fakeSystemClock,
+            systemClock = systemClock,
             logger = biometricUnlockLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
new file mode 100644
index 0000000..97f84c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.annotations.EnableFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL
+import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/**
+ * This includes @[EnableFlags] to work with [SetFlagsRule] to enable all aconfig flags required by
+ * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
+ */
+@EnableFlags(
+    FLAG_SCENE_CONTAINER,
+    FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+    FLAG_KEYGUARD_SHADE_MIGRATION_NSSL,
+    FLAG_MEDIA_IN_SCENE_CONTAINER,
+)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class EnableSceneContainer
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index abadaf7..7b36a29 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -30,7 +30,13 @@
  * Fixture supplying a shared [FakeFeatureFlagsClassic] instance. Can be accessed in tests in order
  * to override flag values.
  */
-val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.fakeFeatureFlagsClassic by
+    Kosmos.Fixture {
+        FakeFeatureFlagsClassic().apply {
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            set(Flags.NSSL_DEBUG_LINES, false)
+        }
+    }
 
 /**
  * Fixture supplying a real [FeatureFlagsClassicRelease] instance, for use by tests that want to
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
new file mode 100644
index 0000000..3faa6eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Should always be used with [SetFlagsRule] and should be ordered after it.
+ *
+ * Used to ensure tests annotated with [EnableSceneContainer] can actually get `true` from
+ * [SceneContainerFlag.isEnabled].
+ */
+class SceneContainerRule : TestRule {
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                val initialEnabledValue = Flags.SCENE_CONTAINER_ENABLED
+                val hasAnnotation =
+                    description?.testClass?.getAnnotation(EnableSceneContainer::class.java) !=
+                        null || description?.getAnnotation(EnableSceneContainer::class.java) != null
+                if (hasAnnotation) {
+                    Assume.assumeTrue(
+                        "Compose must be available for @EnableSceneContainer test",
+                        ComposeFacade.isComposeAvailable()
+                    )
+                    Assume.assumeTrue(
+                        "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test",
+                        trySetSceneContainerEnabled(true)
+                    )
+                    Assert.assertTrue(
+                        "SceneContainerFlag.isEnabled is false:" +
+                            "\n * Did you forget to add a new aconfig flag dependency in" +
+                            " @EnableSceneContainer?" +
+                            "\n * Did you forget to use SetFlagsRule with an earlier order?",
+                        SceneContainerFlag.isEnabled
+                    )
+                }
+                try {
+                    base?.evaluate()
+                } finally {
+                    if (hasAnnotation) {
+                        trySetSceneContainerEnabled(initialEnabledValue)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        fun trySetSceneContainerEnabled(enabled: Boolean): Boolean {
+            if (Flags.SCENE_CONTAINER_ENABLED == enabled) {
+                return true
+            }
+            return try {
+                // TODO(b/283300105): remove this reflection setting once the hard-coded
+                //  Flags.SCENE_CONTAINER_ENABLED is no longer needed.
+                val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
+                field.isAccessible = true
+                field.set(null, enabled) // note: this does not work with multivalent tests
+                true
+            } catch (t: Throwable) {
+                Log.e("SceneContainerRule", "Unable to set SCENE_CONTAINER_ENABLED=$enabled", t)
+                false
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
index d98f496..5c5016d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.jank
 
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index cac2646..73b7c50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,7 +16,20 @@
 
 package com.android.systemui.plugins.statusbar
 
+import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.uiEventLogger
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() }
+var Kosmos.statusBarStateController by
+    Kosmos.Fixture {
+        StatusBarStateControllerImpl(
+            uiEventLogger,
+            interactionJankMonitor,
+            mock(),
+        ) {
+            shadeInteractor
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 09ab655..d314a25 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,182 +16,108 @@
 
 package com.android.systemui.scene
 
-import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager
 import android.content.Context
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.BitmapDrawable
-import android.telecom.TelecomManager
-import android.telephony.PinResult
-import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS
-import android.telephony.TelephonyManager
-import android.telephony.euicc.EuiccManager
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
+import android.content.applicationContext
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.data.repository.BouncerRepository
-import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.doze.DozeLogger
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.data.repository.SceneContainerRepository
+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.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.data.repository.TelephonyRepository
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.currentTime
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
 
 /**
  * Utilities for creating scene container framework related repositories, interactors, and
  * view-models for tests.
  */
 @OptIn(ExperimentalCoroutinesApi::class)
-class SceneTestUtils(
-    private val context: Context,
-) {
-    constructor(test: SysuiTestCase) : this(context = test.context)
+@Deprecated("Please use Kosmos instead.")
+class SceneTestUtils {
 
     val kosmos = Kosmos()
-    val testDispatcher = kosmos.testDispatcher
-    val testScope = kosmos.testScope
-    val featureFlags =
-        FakeFeatureFlagsClassic().apply {
-            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-            set(Flags.NSSL_DEBUG_LINES, false)
-        }
-    val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
-    val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
-    val authenticationRepository: FakeAuthenticationRepository by lazy {
-        FakeAuthenticationRepository(
-            currentTime = { testScope.currentTime },
-        )
-    }
-    val configurationRepository: FakeConfigurationRepository by lazy {
-        FakeConfigurationRepository()
-    }
-    val configurationInteractor: ConfigurationInteractor by lazy {
-        ConfigurationInteractor(configurationRepository)
-    }
-    private val emergencyServicesRepository: EmergencyServicesRepository by lazy {
-        EmergencyServicesRepository(
-            applicationScope = applicationScope(),
-            resources = context.resources,
-            configurationRepository = configurationRepository,
-        )
-    }
-    val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
-    val bouncerRepository = BouncerRepository(featureFlags)
-    val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
-    val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
-    val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
-    val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() }
 
-    val clock: SystemClock = mock {
-        whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
-    }
-    val telephonyManager: TelephonyManager = mock {
-        whenever(createForSubscriptionId(anyInt())).thenReturn(this)
-        whenever(supplyIccLockPin(anyString())).thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3))
-    }
-    val devicePolicyManager: DevicePolicyManager = mock {}
-    val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy {
-        FakeMobileConnectionsRepository(mock(), mock())
+    constructor(
+        context: Context,
+    ) {
+        kosmos.applicationContext = context
     }
 
-    val simBouncerInteractor =
-        SimBouncerInteractor(
-            applicationContext = context,
-            backgroundDispatcher = testDispatcher,
-            applicationScope = applicationScope(),
-            repository = simBouncerRepository,
-            telephonyManager = telephonyManager,
-            resources = context.resources,
-            keyguardUpdateMonitor = mock(),
-            euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
-            mobileConnectionsRepository = mobileConnectionsRepository,
-        )
-
-    val userRepository: FakeUserRepository by lazy {
-        FakeUserRepository().apply {
-            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
-            setUserInfos(users)
-            runBlocking { setSelectedUserInfo(users.first()) }
-        }
+    constructor(testCase: SysuiTestCase) : this(context = testCase.context) {
+        kosmos.testCase = testCase
     }
 
-    private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
-    private var falsingInteractor: FalsingInteractor? = null
-    private var powerInteractor: PowerInteractor? = null
+    val testDispatcher by lazy { kosmos.testDispatcher }
+    val testScope by lazy { kosmos.testScope }
+    val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+    val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+    val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
+    val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository }
+    val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    val configurationInteractor by lazy { kosmos.configurationInteractor }
+    val telephonyRepository by lazy { kosmos.fakeTelephonyRepository }
+    val bouncerRepository by lazy { kosmos.bouncerRepository }
+    val communalRepository by lazy { kosmos.fakeCommunalRepository }
+    val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    val powerRepository by lazy { kosmos.fakePowerRepository }
+    val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository }
+    val clock by lazy { kosmos.systemClock }
+    val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+    val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+    val statusBarStateController by lazy { kosmos.statusBarStateController }
+    val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+    val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
 
-    fun fakeSceneContainerRepository(
-        containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
-    ): SceneContainerRepository {
-        return SceneContainerRepository(applicationScope(), containerConfig)
+    fun fakeSceneContainerRepository(): SceneContainerRepository {
+        return kosmos.sceneContainerRepository
     }
 
     fun fakeSceneKeys(): List<SceneKey> {
@@ -202,222 +128,59 @@
         return kosmos.sceneContainerConfig
     }
 
-    @JvmOverloads
-    fun sceneInteractor(
-        repository: SceneContainerRepository = fakeSceneContainerRepository()
-    ): SceneInteractor {
-        return SceneInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            powerInteractor = powerInteractor(),
-            logger = mock(),
-        )
+    fun sceneInteractor(): SceneInteractor {
+        return kosmos.sceneInteractor
     }
 
-    fun deviceEntryInteractor(
-        repository: DeviceEntryRepository = deviceEntryRepository,
-        authenticationInteractor: AuthenticationInteractor,
-        sceneInteractor: SceneInteractor,
-        faceAuthRepository: DeviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
-        trustRepository: TrustRepository = FakeTrustRepository(),
-    ): DeviceEntryInteractor {
-        return DeviceEntryInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-            deviceEntryFaceAuthRepository = faceAuthRepository,
-            trustRepository = trustRepository,
-            flags = FakeSceneContainerFlags(enabled = true)
-        )
+    fun deviceEntryInteractor(): DeviceEntryInteractor {
+        return kosmos.deviceEntryInteractor
     }
 
-    fun authenticationInteractor(
-        repository: AuthenticationRepository = authenticationRepository,
-    ): AuthenticationInteractor {
-        return AuthenticationInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            selectedUserInteractor = selectedUserInteractor(),
-        )
+    fun authenticationInteractor(): AuthenticationInteractor {
+        return kosmos.authenticationInteractor
     }
 
-    fun keyguardInteractor(
-        repository: KeyguardRepository = keyguardRepository
-    ): KeyguardInteractor {
-        return KeyguardInteractor(
-            repository = repository,
-            commandQueue = FakeCommandQueue(),
-            sceneContainerFlags = sceneContainerFlags,
-            bouncerRepository = FakeKeyguardBouncerRepository(),
-            configurationInteractor = configurationInteractor,
-            shadeRepository = FakeShadeRepository(),
-            sceneInteractorProvider = { sceneInteractor() },
-            powerInteractor = PowerInteractorFactory.create().powerInteractor,
-        )
+    fun keyguardInteractor(): KeyguardInteractor {
+        return kosmos.keyguardInteractor
     }
 
     fun communalInteractor(): CommunalInteractor {
-        return CommunalInteractorFactory.create(
-                communalRepository = communalRepository,
-            )
-            .communalInteractor
+        return kosmos.communalInteractor
     }
 
-    fun bouncerInteractor(
-        authenticationInteractor: AuthenticationInteractor,
-        deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor = mock(),
-    ): BouncerInteractor {
-        return BouncerInteractor(
-            applicationScope = applicationScope(),
-            applicationContext = context,
-            repository = bouncerRepository,
-            authenticationInteractor = authenticationInteractor,
-            deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
-            falsingInteractor = falsingInteractor(),
-            powerInteractor = powerInteractor(),
-            simBouncerInteractor = simBouncerInteractor,
-        )
+    fun bouncerInteractor(): BouncerInteractor {
+        return kosmos.bouncerInteractor
     }
 
-    fun keyguardRootViewModel(): KeyguardRootViewModel = mock()
-
     fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
-        return NotificationsPlaceholderViewModel(
-            interactor =
-                NotificationStackAppearanceInteractor(
-                    repository = NotificationStackAppearanceRepository(),
-                ),
-            flags = sceneContainerFlags,
-            featureFlags = featureFlags,
-        )
+        return kosmos.notificationsPlaceholderViewModel
     }
 
-    fun bouncerViewModel(
-        bouncerInteractor: BouncerInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        actionButtonInteractor: BouncerActionButtonInteractor = bouncerActionButtonInteractor(),
-        users: List<UserViewModel> = createUsers(),
-    ): BouncerViewModel {
-        return BouncerViewModel(
-            applicationContext = context,
-            applicationScope = applicationScope(),
-            mainDispatcher = testDispatcher,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            flags = sceneContainerFlags,
-            selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
-            users = flowOf(users),
-            userSwitcherMenu = flowOf(createMenuActions()),
-            actionButton = actionButtonInteractor.actionButton,
-            clock = clock,
-            devicePolicyManager = devicePolicyManager,
-        )
+    fun bouncerViewModel(): BouncerViewModel {
+        return kosmos.bouncerViewModel
     }
 
-    fun telephonyInteractor(
-        repository: TelephonyRepository = telephonyRepository,
-    ): TelephonyInteractor {
-        return TelephonyInteractor(repository = repository)
+    fun telephonyInteractor(): TelephonyInteractor {
+        return kosmos.telephonyInteractor
     }
 
-    fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
-        return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
+    fun falsingInteractor(): FalsingInteractor {
+        return kosmos.falsingInteractor
     }
 
     fun falsingCollector(): FalsingCollector {
-        return falsingCollectorFake
+        return kosmos.falsingCollector
     }
 
-    fun powerInteractor(
-        repository: PowerRepository = powerRepository,
-        falsingCollector: FalsingCollector = falsingCollector(),
-        screenOffAnimationController: ScreenOffAnimationController = mock(),
-        statusBarStateController: StatusBarStateController = mock(),
-    ): PowerInteractor {
-        return powerInteractor
-            ?: PowerInteractor(
-                    repository = repository,
-                    falsingCollector = falsingCollector,
-                    screenOffAnimationController = screenOffAnimationController,
-                    statusBarStateController = statusBarStateController,
-                )
-                .also { powerInteractor = it }
-    }
-
-    private fun applicationScope(): CoroutineScope {
-        return testScope.backgroundScope
-    }
-
-    private fun createUsers(
-        count: Int = 3,
-        selectedIndex: Int = 0,
-    ): List<UserViewModel> {
-        check(selectedIndex in 0 until count)
-
-        return buildList {
-            repeat(count) { index ->
-                add(
-                    UserViewModel(
-                        viewKey = index,
-                        name = Text.Loaded("name_$index"),
-                        image = BitmapDrawable(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)),
-                        isSelectionMarkerVisible = index == selectedIndex,
-                        alpha = 1f,
-                        onClicked = {},
-                    )
-                )
-            }
-        }
-    }
-
-    private fun createMenuActions(): List<UserActionViewModel> {
-        return buildList {
-            repeat(3) { index ->
-                add(
-                    UserActionViewModel(
-                        viewKey = index.toLong(),
-                        iconResourceId = 0,
-                        textResourceId = 0,
-                        onClicked = {},
-                    )
-                )
-            }
-        }
+    fun powerInteractor(): PowerInteractor {
+        return kosmos.powerInteractor
     }
 
     fun selectedUserInteractor(): SelectedUserInteractor {
-        return SelectedUserInteractor(userRepository)
+        return kosmos.selectedUserInteractor
     }
 
-    fun bouncerActionButtonInteractor(
-        mobileConnectionsRepository: MobileConnectionsRepository = mock(),
-        activityTaskManager: ActivityTaskManager = mock(),
-        telecomManager: TelecomManager? = null,
-        emergencyAffordanceManager: EmergencyAffordanceManager =
-            EmergencyAffordanceManager(context),
-        emergencyDialerIntentFactory: EmergencyDialerIntentFactory =
-            object : EmergencyDialerIntentFactory {
-                override fun invoke(): Intent = Intent()
-            },
-        metricsLogger: MetricsLogger = mock(),
-        dozeLogger: DozeLogger = mock(),
-    ): BouncerActionButtonInteractor {
-        return BouncerActionButtonInteractor(
-            applicationContext = context,
-            backgroundDispatcher = testDispatcher,
-            repository = emergencyServicesRepository,
-            mobileConnectionsRepository = mobileConnectionsRepository,
-            telephonyInteractor = telephonyInteractor(),
-            authenticationInteractor = authenticationInteractor(),
-            selectedUserInteractor = selectedUserInteractor(),
-            activityTaskManager = activityTaskManager,
-            telecomManager = telecomManager,
-            emergencyAffordanceManager = emergencyAffordanceManager,
-            emergencyDialerIntentFactory = emergencyDialerIntentFactory,
-            metricsLogger = metricsLogger,
-            dozeLogger = dozeLogger,
-        )
+    fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor {
+        return kosmos.bouncerActionButtonInteractor
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
index 7c4e160..e19941c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.scene.shared.model.sceneContainerConfig
+import com.android.systemui.scene.sceneContainerConfig
 
 val Kosmos.sceneContainerRepository by
     Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
index c2cdbed..979d8e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
index b4fc948..8811b8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
@@ -30,6 +30,7 @@
                     SceneKey.Lockscreen,
                     SceneKey.Bouncer,
                     SceneKey.Gone,
+                    SceneKey.Communal,
                 ),
             initialSceneKey = SceneKey.Lockscreen,
         ),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
index e671d45..0e4c923 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.smartspace.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-var Kosmos.smartspaceRepository: SmartspaceRepository by Kosmos.Fixture { fakeSmartspaceRepository }
-val Kosmos.fakeSmartspaceRepository by Kosmos.Fixture { FakeSmartspaceRepository() }
+val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() }
+
+val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
index 93a7adf..8385403 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
 val Kosmos.lockscreenShadeScrimTransitionController by Fixture {
     LockscreenShadeScrimTransitionController(
         scrimController = scrimController,
-        context = testableContext,
+        context = applicationContext,
         configurationController = configurationController,
         dumpManager = dumpManager,
         splitShadeStateController = splitShadeStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index 2752cc2..1c6ce79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.classifier.falsingCollector
 import com.android.systemui.classifier.falsingManager
 import com.android.systemui.dump.dumpManager
@@ -47,7 +47,7 @@
         scrimTransitionController = lockscreenShadeScrimTransitionController,
         keyguardTransitionControllerFactory = lockscreenShadeKeyguardTransitionControllerFactory,
         depthController = notificationShadeDepthController,
-        context = testableContext,
+        context = applicationContext,
         splitShadeOverScrollerFactory = splitShadeLockScreenOverScrollerFactory,
         singleShadeOverScrollerFactory = singleShadeLockScreenOverScrollerFactory,
         activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
index d98f496..9b27a9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.collection
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notifCollection by Fixture { mockNotifCollection }
+val Kosmos.mockNotifCollection by Fixture { mock<NotifCollection>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 9851b0e..9c5c486 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.drawable.Icon
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
 
 /** Simple ActiveNotificationModel builder for use in tests. */
 fun activeNotificationModel(
@@ -32,6 +33,11 @@
     aodIcon: Icon? = null,
     shelfIcon: Icon? = null,
     statusBarIcon: Icon? = null,
+    uid: Int = 0,
+    instanceId: Int? = null,
+    isGroupSummary: Boolean = false,
+    packageName: String = "pkg",
+    bucket: Int = BUCKET_UNKNOWN,
 ) =
     ActiveNotificationModel(
         key = key,
@@ -45,4 +51,9 @@
         aodIcon = aodIcon,
         shelfIcon = shelfIcon,
         statusBarIcon = statusBarIcon,
+        uid = uid,
+        packageName = packageName,
+        instanceId = instanceId,
+        isGroupSummary = isGroupSummary,
+        bucket = bucket,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
index cb1ba20..b40e1e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
@@ -20,11 +20,20 @@
 
 /**
  * Make the repository hold [count] active notifications for testing. The keys of the notifications
- * are "0", "1", ..., (count - 1).toString().
+ * are "0", "1", ..., (count - 1).toString(). The ranks are the same values in Int.
  */
 fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
     this.activeNotifications.value =
         ActiveNotificationsStore.Builder()
-            .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } }
+            .apply {
+                val rankingsMap = mutableMapOf<String, Int>()
+                repeat(count) { i ->
+                    val key = "$i"
+                    addEntry(activeNotificationModel(key = key))
+                    rankingsMap[key] = i
+                }
+
+                setRankingsMap(rankingsMap)
+            }
             .build()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index 67fecb4..acc455f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -19,8 +19,8 @@
 import com.android.systemui.common.ui.configurationState
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.collection.notifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection
 import com.android.systemui.statusbar.ui.systemBarUtilsState
 
 val Kosmos.notificationIconContainerShelfViewBinder by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
index d98f496..30fc2f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.logging
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationPanelLogger by Fixture { mockNotificationPanelLogger }
+val Kosmos.mockNotificationPanelLogger by Fixture { mock<NotificationPanelLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
index 83ac330..7f6f698 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.ambientState by Fixture {
     AmbientState(
-        /*context=*/ testableContext,
+        /*context=*/ applicationContext,
         /*dumpManager=*/ dumpManager,
         /*sectionProvider=*/ stackScrollAlgorithmSectionProvider,
         /*bypassController=*/ stackScrollAlgorithmBypassController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt
new file mode 100644
index 0000000..de52155
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.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.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+
+val Kosmos.notificationStatsLogger by Fixture {
+    NotificationStatsLoggerImpl(
+        applicationScope = testScope,
+        bgDispatcher = testDispatcher,
+        statusBarService = statusBarService,
+        notificationListenerService = notificationListenerService,
+        notificationPanelLogger = notificationPanelLogger,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 04716b9..c6498e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,8 +23,10 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
 import com.android.systemui.statusbar.phone.notificationIconAreaController
+import java.util.Optional
 
 val Kosmos.notificationListViewBinder by Fixture {
     NotificationListViewBinder(
@@ -35,5 +37,6 @@
         iconAreaController = notificationIconAreaController,
         metricsLogger = metricsLogger,
         nicBinder = notificationIconContainerShelfViewBinder,
+        loggerOptional = Optional.of(notificationStatsLogger),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
index d98f496..08bda46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.notificationListLoggerViewModel by Fixture {
+    NotificationLoggerViewModel(
+        keyguardInteractor = keyguardInteractor,
+        windowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor,
+        activeNotificationsInteractor = activeNotificationsInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index f5a4c03..998e579 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -33,6 +33,7 @@
         shelf = notificationShelfViewModel,
         hideListViewModel = hideListViewModel,
         footer = Optional.of(footerViewModel),
+        logger = Optional.of(notificationListLoggerViewModel),
         activeNotificationsInteractor = activeNotificationsInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         seenNotificationsInteractor = seenNotificationsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index e4313bb1..d9beabb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
@@ -27,7 +27,7 @@
 
 val Kosmos.windowRootViewVisibilityInteractor by Fixture {
     WindowRootViewVisibilityInteractor(
-        scope = testScope,
+        scope = applicationCoroutineScope,
         windowRootViewVisibilityRepository = windowRootViewVisibilityRepository,
         keyguardRepository = keyguardRepository,
         headsUpManager = headsUpManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
index d98f496..9d62ea5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.fakeMobileConnectionsRepository by Fixture {
+    FakeMobileConnectionsRepository(tableLogBuffer = mock())
+}
+
+val Kosmos.mobileConnectionsRepository by
+    Fixture<MobileConnectionsRepository> { fakeMobileConnectionsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
index d98f496..0b9f897 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.systemui.user.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.user.domain.interactor.guestUserInteractor
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+val Kosmos.userSwitcherViewModel by Fixture {
+    UserSwitcherViewModel(
+        userSwitcherInteractor = userSwitcherInteractor,
+        guestUserInteractor = guestUserInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
index 1f48d94..11c09ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
@@ -17,6 +17,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.util.time.FakeSystemClock
 import dagger.Binds
 import dagger.Module
@@ -27,8 +28,9 @@
 interface FakeExecutorModule {
     @Binds @Main @SysUISingleton fun bindMainExecutor(executor: FakeExecutor): Executor
 
+    @Binds @UiBackground @SysUISingleton fun bindUiBgExecutor(executor: FakeExecutor): Executor
+
     companion object {
-        @Provides
-        fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
+        @Provides fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 914e654..f3a8b14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -17,5 +17,19 @@
 package com.android.systemui.util.time
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.currentTime
 
-var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.systemClock by
+    Kosmos.Fixture<SystemClock> {
+        mock {
+            whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
+            whenever(uptimeMillis()).thenAnswer { testScope.currentTime }
+        }
+    }
+
+val Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
index d98f496..4e0c0883 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+package com.android.telecom
 
+import android.telecom.TelecomManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
+var Kosmos.telecomManager by Fixture<TelecomManager?> { mock() }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6a81425..a856f42 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4271,13 +4271,19 @@
                 if (value != null) {
                     viewState.setCurrentValue(value);
                 }
-
+                boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                 if (shouldRequestSecondaryProvider(flags)) {
                     if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
                             id, viewState, flags)) {
                         Slog.v(TAG, "Started a new fill request for secondary provider.");
                         return;
                     }
+
+                    FillResponse response = viewState.getSecondaryResponse();
+                    if (response != null) {
+                        logPresentationStatsOnViewEntered(response, isCredmanRequested);
+                    }
+
                     // If the ViewState is ready to be displayed, onReady() will be called.
                     viewState.update(value, virtualBounds, flags);
 
@@ -4363,15 +4369,9 @@
                     return;
                 }
 
-                if (viewState.getResponse() != null) {
-                    boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
-                    FillResponse response = viewState.getResponse();
-                    mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
-                    mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
-                    mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
-                            mFieldClassificationIdSnapshot);
-                    mPresentationStatsEventLogger.maybeSetAvailableCount(
-                            response.getDatasets(), mCurrentViewId);
+                FillResponse response = viewState.getResponse();
+                if (response != null) {
+                    logPresentationStatsOnViewEntered(response, isCredmanRequested);
                 }
 
                 if (isSameViewEntered) {
@@ -4412,6 +4412,17 @@
     }
 
     @GuardedBy("mLock")
+    private void logPresentationStatsOnViewEntered(FillResponse response,
+            boolean isCredmanRequested) {
+        mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+        mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
+        mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
+                mFieldClassificationIdSnapshot);
+        mPresentationStatsEventLogger.maybeSetAvailableCount(
+                response.getDatasets(), mCurrentViewId);
+    }
+
+    @GuardedBy("mLock")
     private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
         if ((viewState.getState()
                 & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 3dcea19..f8a9867 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -77,6 +77,9 @@
     @AuthenticateOptions.DisplayState
     int getDisplayState();
 
+    /** Gets whether touches on sensor are ignored by HAL */
+    boolean isHardwareIgnoringTouches();
+
     /**
      * Subscribe to context changes.
      *
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 95a047f..535b7b7 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -86,8 +86,8 @@
     @Nullable private final Handler mHandler;
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
-
     private int mDisplayState = AuthenticateOptions.DISPLAY_STATE_UNKNOWN;
+    private boolean mIsHardwareIgnoringTouches = false;
     @VisibleForTesting
     final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
         @Override
@@ -129,6 +129,14 @@
                         notifyChanged();
                     }
                 }
+
+                @Override
+                public void onHardwareIgnoreTouchesChanged(boolean shouldIgnore) {
+                    if (mIsHardwareIgnoringTouches != shouldIgnore) {
+                        mIsHardwareIgnoringTouches = shouldIgnore;
+                        notifyChanged();
+                    }
+                }
             });
             service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
                 @Override
@@ -215,6 +223,11 @@
     }
 
     @Override
+    public boolean isHardwareIgnoringTouches() {
+        return mIsHardwareIgnoringTouches;
+    }
+
+    @Override
     public void subscribe(@NonNull OperationContextExt context,
             @NonNull Consumer<OperationContext> consumer) {
         mSubscribers.put(context, consumer);
@@ -254,6 +267,7 @@
                 + "bp session: " + getBiometricPromptSessionInfo() + ", "
                 + "displayState: " + getDisplayState() + ", "
                 + "isAwake: " + isAwake() +  ", "
+                + "isHardwareIgnoring: " + isHardwareIgnoringTouches() +  ", "
                 + "isDisplayOn: " + isDisplayOn() +  ", "
                 + "dock: " + getDockedState() + ", "
                 + "rotation: " + getCurrentRotation() + ", "
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index b4e0dff..0045d44 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -20,12 +20,14 @@
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.DisplayState;
 import android.hardware.biometrics.common.FoldState;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.common.WakeReason;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -51,13 +53,26 @@
 
     /** Create a context. */
     public OperationContextExt(boolean isBP) {
-        this(new OperationContext(), isBP);
+        this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE);
+    }
+
+    public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality) {
+        this(new OperationContext(), isBP, modality);
     }
 
     /** Create a wrapped context. */
-    public OperationContextExt(@NonNull OperationContext context, boolean isBP) {
+    public OperationContextExt(@NonNull OperationContext context, boolean isBP,
+            @BiometricAuthenticator.Modality int modality) {
         mAidlContext = context;
         mIsBP = isBP;
+
+        if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
+            mAidlContext.operationState = OperationState.fingerprintOperationState(
+                    new OperationState.FingerprintOperationState());
+        } else if (modality == BiometricAuthenticator.TYPE_FACE) {
+            mAidlContext.operationState = OperationState.faceOperationState(
+                    new OperationState.FaceOperationState());
+        }
     }
 
     /**
@@ -247,12 +262,23 @@
         return mOrientation;
     }
 
+    /** The current operation state  */
+    public OperationState getOperationState() {
+        return mAidlContext.operationState;
+    }
+
     /** Update this object with the latest values from the given context. */
     OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
         mAidlContext.isAod = biometricContext.isAod();
         mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
         mAidlContext.foldState = toAidlFoldState(biometricContext.getFoldState());
         mAidlContext.isCrypto = isCrypto;
+
+        if (mAidlContext.operationState != null && mAidlContext.operationState.getTag()
+                == OperationState.fingerprintOperationState) {
+            mAidlContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches =
+                    biometricContext.isHardwareIgnoringTouches();
+        }
         setFirstSessionId(biometricContext);
 
         mIsDisplayOn = biometricContext.isDisplayOn();
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 1a682a9..1fc7c70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -41,22 +41,42 @@
  * a common interface.
  */
 public class ClientMonitorCallbackConverter {
-    private IBiometricSensorReceiver mSensorReceiver; // BiometricService
-    private IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
-    private IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
+    private final IBiometricSensorReceiver mSensorReceiver; // BiometricService
+    private final IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
+    private final IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
 
     public ClientMonitorCallbackConverter(IBiometricSensorReceiver sensorReceiver) {
         mSensorReceiver = sensorReceiver;
+        mFaceServiceReceiver = null;
+        mFingerprintServiceReceiver = null;
     }
 
     public ClientMonitorCallbackConverter(IFaceServiceReceiver faceServiceReceiver) {
+        mSensorReceiver = null;
         mFaceServiceReceiver = faceServiceReceiver;
+        mFingerprintServiceReceiver = null;
     }
 
     public ClientMonitorCallbackConverter(IFingerprintServiceReceiver fingerprintServiceReceiver) {
+        mSensorReceiver = null;
+        mFaceServiceReceiver = null;
         mFingerprintServiceReceiver = fingerprintServiceReceiver;
     }
 
+    /**
+     * Returns an int representing the {@link BiometricAuthenticator.Modality} of the active
+     * ServiceReceiver
+     */
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        if (mFaceServiceReceiver != null) {
+            return BiometricAuthenticator.TYPE_FACE;
+        } else if (mFingerprintServiceReceiver != null) {
+            return BiometricAuthenticator.TYPE_FINGERPRINT;
+        }
+        return BiometricAuthenticator.TYPE_NONE;
+    }
+
     // The following apply to all clients
 
     public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 03658ce..0f01510 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.IBinder;
 
 import com.android.server.biometrics.log.BiometricContext;
@@ -58,7 +59,8 @@
         super(context, token, listener, userId, owner, cookie, sensorId,
                 biometricLogger, biometricContext);
         mLazyDaemon = lazyDaemon;
-        mOperationContext = new OperationContextExt(isBiometricPrompt());
+        int modality = listener != null ? listener.getModality() : BiometricAuthenticator.TYPE_NONE;
+        mOperationContext = new OperationContextExt(isBiometricPrompt(), modality);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 29c5a3d..145885d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
@@ -326,6 +327,12 @@
             if (session.hasContextMethods()) {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index e58e5ae..3aab7b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -122,6 +123,12 @@
             getBiometricContext().subscribe(opContext, ctx -> {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c0761ed..bf5011d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -220,6 +221,12 @@
             getBiometricContext().subscribe(opContext, ctx -> {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 898d5a5..ad27c52 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -53,8 +53,7 @@
     @GuardedBy("ImfLock.class")
     void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
         mSubtypeHandles.clear();
-        final InputMethodUtils.InputMethodSettings settings =
-                new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
+        final InputMethodSettings settings = new InputMethodSettings(methodMap, mUserId);
         for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
             if (!imi.shouldShowInInputMethodPicker()) {
                 continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4db9ead..622a2de 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -183,7 +183,6 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.utils.PriorityDump;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
new file mode 100644
index 0000000..4c7d755
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.os.LocaleList;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Printer;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Utility class for putting and getting settings for InputMethod.
+ *
+ * <p>This is used in two ways:</p>
+ * <ul>
+ *     <li>Singleton instance in {@link InputMethodManagerService}, which is updated on
+ *     user-switch to follow the current user.</li>
+ *     <li>On-demand instances when we need settings for non-current users.</li>
+ * </ul>
+ */
+final class InputMethodSettings {
+    public static final boolean DEBUG = false;
+    private static final String TAG = "InputMethodSettings";
+
+    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+    private static final char INPUT_METHOD_SEPARATOR = InputMethodUtils.INPUT_METHOD_SEPARATOR;
+    private static final char INPUT_METHOD_SUBTYPE_SEPARATOR =
+            InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR;
+
+    private final ArrayMap<String, InputMethodInfo> mMethodMap;
+    @UserIdInt
+    private final int mCurrentUserId;
+
+    private static void buildEnabledInputMethodsSettingString(
+            StringBuilder builder, Pair<String, ArrayList<String>> ime) {
+        builder.append(ime.first);
+        // Inputmethod and subtypes are saved in the settings as follows:
+        // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+        for (String subtypeId : ime.second) {
+            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+        }
+    }
+
+    InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+        mMethodMap = methodMap;
+        mCurrentUserId = userId;
+        String ime = getSelectedInputMethod();
+        String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+        if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+            putSelectedInputMethod(defaultDeviceIme);
+            putSelectedDefaultDeviceInputMethod(null);
+        }
+    }
+
+    private void putString(@NonNull String key, @Nullable String str) {
+        SecureSettingsWrapper.putString(key, str, mCurrentUserId);
+    }
+
+    @Nullable
+    private String getString(@NonNull String key, @Nullable String defaultValue) {
+        return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
+    }
+
+    private void putInt(String key, int value) {
+        SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
+    }
+
+    private int getInt(String key, int defaultValue) {
+        return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
+    }
+
+    ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+        return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
+    }
+
+    @NonNull
+    ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
+            @Nullable Predicate<InputMethodInfo> matchingCondition) {
+        return createEnabledInputMethodListLocked(
+                getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
+    }
+
+    List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+            InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
+        List<InputMethodSubtype> enabledSubtypes =
+                getEnabledInputMethodSubtypeListLocked(imi);
+        if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
+            enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+                    SystemLocaleWrapper.get(mCurrentUserId), imi);
+        }
+        return InputMethodSubtype.sort(imi, enabledSubtypes);
+    }
+
+    List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
+        List<Pair<String, ArrayList<String>>> imsList =
+                getEnabledInputMethodsAndSubtypeListLocked();
+        ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+        if (imi != null) {
+            for (Pair<String, ArrayList<String>> imsPair : imsList) {
+                InputMethodInfo info = mMethodMap.get(imsPair.first);
+                if (info != null && info.getId().equals(imi.getId())) {
+                    final int subtypeCount = info.getSubtypeCount();
+                    for (int i = 0; i < subtypeCount; ++i) {
+                        InputMethodSubtype ims = info.getSubtypeAt(i);
+                        for (String s : imsPair.second) {
+                            if (String.valueOf(ims.hashCode()).equals(s)) {
+                                enabledSubtypes.add(ims);
+                            }
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+        return enabledSubtypes;
+    }
+
+    List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+        final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+        final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter subtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+        final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+        if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+            return imsList;
+        }
+        inputMethodSplitter.setString(enabledInputMethodsStr);
+        while (inputMethodSplitter.hasNext()) {
+            String nextImsStr = inputMethodSplitter.next();
+            subtypeSplitter.setString(nextImsStr);
+            if (subtypeSplitter.hasNext()) {
+                ArrayList<String> subtypeHashes = new ArrayList<>();
+                // The first element is ime id.
+                String imeId = subtypeSplitter.next();
+                while (subtypeSplitter.hasNext()) {
+                    subtypeHashes.add(subtypeSplitter.next());
+                }
+                imsList.add(new Pair<>(imeId, subtypeHashes));
+            }
+        }
+        return imsList;
+    }
+
+    /**
+     * Build and put a string of EnabledInputMethods with removing specified Id.
+     *
+     * @return the specified id was removed or not.
+     */
+    boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+            StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+        boolean isRemoved = false;
+        boolean needsAppendSeparator = false;
+        for (Pair<String, ArrayList<String>> ims : imsList) {
+            String curId = ims.first;
+            if (curId.equals(id)) {
+                // We are disabling this input method, and it is
+                // currently enabled.  Skip it to remove from the
+                // new list.
+                isRemoved = true;
+            } else {
+                if (needsAppendSeparator) {
+                    builder.append(INPUT_METHOD_SEPARATOR);
+                } else {
+                    needsAppendSeparator = true;
+                }
+                buildEnabledInputMethodsSettingString(builder, ims);
+            }
+        }
+        if (isRemoved) {
+            // Update the setting with the new list of input methods.
+            putEnabledInputMethodsStr(builder.toString());
+        }
+        return isRemoved;
+    }
+
+    private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
+            List<Pair<String, ArrayList<String>>> imsList,
+            Predicate<InputMethodInfo> matchingCondition) {
+        final ArrayList<InputMethodInfo> res = new ArrayList<>();
+        for (Pair<String, ArrayList<String>> ims : imsList) {
+            InputMethodInfo info = mMethodMap.get(ims.first);
+            if (info != null && !info.isVrOnly()
+                    && (matchingCondition == null || matchingCondition.test(info))) {
+                res.add(info);
+            }
+        }
+        return res;
+    }
+
+    void putEnabledInputMethodsStr(@Nullable String str) {
+        if (DEBUG) {
+            Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+        }
+        if (TextUtils.isEmpty(str)) {
+            // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
+            // empty data scenario.
+            putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
+        } else {
+            putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
+        }
+    }
+
+    @NonNull
+    String getEnabledInputMethodsStr() {
+        return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
+    }
+
+    private void saveSubtypeHistory(
+            List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+        StringBuilder builder = new StringBuilder();
+        boolean isImeAdded = false;
+        if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+            builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+                    newSubtypeId);
+            isImeAdded = true;
+        }
+        for (Pair<String, String> ime : savedImes) {
+            String imeId = ime.first;
+            String subtypeId = ime.second;
+            if (TextUtils.isEmpty(subtypeId)) {
+                subtypeId = NOT_A_SUBTYPE_ID_STR;
+            }
+            if (isImeAdded) {
+                builder.append(INPUT_METHOD_SEPARATOR);
+            } else {
+                isImeAdded = true;
+            }
+            builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+                    subtypeId);
+        }
+        // Remove the last INPUT_METHOD_SEPARATOR
+        putSubtypeHistoryStr(builder.toString());
+    }
+
+    private void addSubtypeToHistory(String imeId, String subtypeId) {
+        List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+        for (Pair<String, String> ime : subtypeHistory) {
+            if (ime.first.equals(imeId)) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+                            + ime.second);
+                }
+                // We should break here
+                subtypeHistory.remove(ime);
+                break;
+            }
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+        }
+        saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+    }
+
+    private void putSubtypeHistoryStr(@NonNull String str) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+        }
+        if (TextUtils.isEmpty(str)) {
+            // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
+            // data scenario.
+            putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
+        } else {
+            putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+        }
+    }
+
+    Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+        // Gets the first one from the history
+        return getLastSubtypeForInputMethodLockedInternal(null);
+    }
+
+    @Nullable
+    InputMethodSubtype getLastInputMethodSubtypeLocked() {
+        final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
+        // TODO: Handle the case of the last IME with no subtypes
+        if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+                || TextUtils.isEmpty(lastIme.second)) {
+            return null;
+        }
+        final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+        if (lastImi == null) return null;
+        try {
+            final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+            final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+                    lastSubtypeHash);
+            if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+                return null;
+            }
+            return lastImi.getSubtypeAt(lastSubtypeId);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    String getLastSubtypeForInputMethodLocked(String imeId) {
+        Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+        if (ime != null) {
+            return ime.second;
+        } else {
+            return null;
+        }
+    }
+
+    private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+        List<Pair<String, ArrayList<String>>> enabledImes =
+                getEnabledInputMethodsAndSubtypeListLocked();
+        List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+        for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+            final String imeInTheHistory = imeAndSubtype.first;
+            // If imeId is empty, returns the first IME and subtype in the history
+            if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+                final String subtypeInTheHistory = imeAndSubtype.second;
+                final String subtypeHashCode =
+                        getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+                                enabledImes, imeInTheHistory, subtypeInTheHistory);
+                if (!TextUtils.isEmpty(subtypeHashCode)) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "Enabled subtype found in the history: " + subtypeHashCode);
+                    }
+                    return new Pair<>(imeInTheHistory, subtypeHashCode);
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "No enabled IME found in the history");
+        }
+        return null;
+    }
+
+    private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+            ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+        final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
+        for (Pair<String, ArrayList<String>> enabledIme : enabledImes) {
+            if (enabledIme.first.equals(imeId)) {
+                final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+                final InputMethodInfo imi = mMethodMap.get(imeId);
+                if (explicitlyEnabledSubtypes.size() == 0) {
+                    // If there are no explicitly enabled subtypes, applicable subtypes are
+                    // enabled implicitly.
+                    // If IME is enabled and no subtypes are enabled, applicable subtypes
+                    // are enabled implicitly, so needs to treat them to be enabled.
+                    if (imi != null && imi.getSubtypeCount() > 0) {
+                        List<InputMethodSubtype> implicitlyEnabledSubtypes =
+                                SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
+                                        imi);
+                        final int numSubtypes = implicitlyEnabledSubtypes.size();
+                        for (int i = 0; i < numSubtypes; ++i) {
+                            final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
+                            if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+                                return subtypeHashCode;
+                            }
+                        }
+                    }
+                } else {
+                    for (String s : explicitlyEnabledSubtypes) {
+                        if (s.equals(subtypeHashCode)) {
+                            // If both imeId and subtypeId are enabled, return subtypeId.
+                            try {
+                                final int hashCode = Integer.parseInt(subtypeHashCode);
+                                // Check whether the subtype id is valid or not
+                                if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
+                                    return s;
+                                } else {
+                                    return NOT_A_SUBTYPE_ID_STR;
+                                }
+                            } catch (NumberFormatException e) {
+                                return NOT_A_SUBTYPE_ID_STR;
+                            }
+                        }
+                    }
+                }
+                // If imeId was enabled but subtypeId was disabled.
+                return NOT_A_SUBTYPE_ID_STR;
+            }
+        }
+        // If both imeId and subtypeId are disabled, return null
+        return null;
+    }
+
+    private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+        ArrayList<Pair<String, String>> imsList = new ArrayList<>();
+        final String subtypeHistoryStr = getSubtypeHistoryStr();
+        if (TextUtils.isEmpty(subtypeHistoryStr)) {
+            return imsList;
+        }
+        final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter subtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+        inputMethodSplitter.setString(subtypeHistoryStr);
+        while (inputMethodSplitter.hasNext()) {
+            String nextImsStr = inputMethodSplitter.next();
+            subtypeSplitter.setString(nextImsStr);
+            if (subtypeSplitter.hasNext()) {
+                String subtypeId = NOT_A_SUBTYPE_ID_STR;
+                // The first element is ime id.
+                String imeId = subtypeSplitter.next();
+                while (subtypeSplitter.hasNext()) {
+                    subtypeId = subtypeSplitter.next();
+                    break;
+                }
+                imsList.add(new Pair<>(imeId, subtypeId));
+            }
+        }
+        return imsList;
+    }
+
+    @NonNull
+    private String getSubtypeHistoryStr() {
+        final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
+        if (DEBUG) {
+            Slog.d(TAG, "getSubtypeHistoryStr: " + history);
+        }
+        return history;
+    }
+
+    void putSelectedInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+                    + mCurrentUserId);
+        }
+        putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+    }
+
+    void putSelectedSubtype(int subtypeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+                    + mCurrentUserId);
+        }
+        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+    }
+
+    @Nullable
+    String getSelectedInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
+        }
+        return imi;
+    }
+
+    @Nullable
+    String getSelectedDefaultDeviceInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
+                    + mCurrentUserId);
+        }
+        return imi;
+    }
+
+    void putSelectedDefaultDeviceInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
+                    + mCurrentUserId);
+        }
+        putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+    }
+
+    void putDefaultVoiceInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
+        }
+        putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
+    }
+
+    @Nullable
+    String getDefaultVoiceInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
+        }
+        return imi;
+    }
+
+    boolean isSubtypeSelected() {
+        return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+    }
+
+    private int getSelectedInputMethodSubtypeHashCode() {
+        return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                NOT_A_SUBTYPE_ID);
+    }
+
+    @UserIdInt
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    int getSelectedInputMethodSubtypeId(String selectedImiId) {
+        final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+        if (imi == null) {
+            return NOT_A_SUBTYPE_ID;
+        }
+        final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+        return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+    }
+
+    void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+            InputMethodSubtype currentSubtype) {
+        String subtypeId = NOT_A_SUBTYPE_ID_STR;
+        if (currentSubtype != null) {
+            subtypeId = String.valueOf(currentSubtype.hashCode());
+        }
+        if (InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+            addSubtypeToHistory(curMethodId, subtypeId);
+        }
+    }
+
+    /**
+     * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
+     * non-current users.
+     *
+     * <p>TODO: Address code duplication between this and
+     * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
+     *
+     * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
+     */
+    @Nullable
+    InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
+        final String selectedMethodId = getSelectedInputMethod();
+        if (selectedMethodId == null) {
+            return null;
+        }
+        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
+        }
+
+        final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+        if (subtypeHashCode != NOT_A_SUBTYPE_ID) {
+            final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+                    subtypeHashCode);
+            if (subtypeIndex >= 0) {
+                return imi.getSubtypeAt(subtypeIndex);
+            }
+        }
+
+        // If there are no selected subtypes, the framework will try to find the most applicable
+        // subtype from explicitly or implicitly enabled subtypes.
+        final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+                getEnabledInputMethodSubtypeListLocked(imi, true);
+        // If there is only one explicitly or implicitly enabled subtype, just returns it.
+        if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
+            return null;
+        }
+        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+            return explicitlyOrImplicitlyEnabledSubtypes.get(0);
+        }
+        final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
+        final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+                locale, true);
+        if (subtype != null) {
+            return subtype;
+        }
+        return SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
+    }
+
+    boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+            @NonNull ArrayList<InputMethodSubtype> subtypes,
+            @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
+        final InputMethodInfo imi = mMethodMap.get(imeId);
+        if (imi == null) {
+            return false;
+        }
+        if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
+                imi.getPackageName())) {
+            return false;
+        }
+
+        if (subtypes.isEmpty()) {
+            additionalSubtypeMap.remove(imi.getId());
+        } else {
+            additionalSubtypeMap.put(imi.getId(), subtypes);
+        }
+        AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
+        return true;
+    }
+
+    boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
+            @NonNull int[] subtypeHashCodes) {
+        final InputMethodInfo imi = mMethodMap.get(imeId);
+        if (imi == null) {
+            return false;
+        }
+
+        final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
+        for (int subtypeHashCode : subtypeHashCodes) {
+            if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
+                continue;  // NOT_A_SUBTYPE_ID must not be saved
+            }
+            if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
+                continue;  // this subtype does not exist in InputMethodInfo.
+            }
+            if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
+                continue;  // The entry is already added.  No need to add anymore.
+            }
+            validSubtypeHashCodes.add(subtypeHashCode);
+        }
+
+        final String originalEnabledImesString = getEnabledInputMethodsStr();
+        final String updatedEnabledImesString = updateEnabledImeString(
+                originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
+        if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
+            return false;
+        }
+
+        putEnabledInputMethodsStr(updatedEnabledImesString);
+        return true;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static String updateEnabledImeString(@NonNull String enabledImesString,
+            @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
+        final TextUtils.SimpleStringSplitter imeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+
+        final StringBuilder sb = new StringBuilder();
+
+        imeSplitter.setString(enabledImesString);
+        boolean needsImeSeparator = false;
+        while (imeSplitter.hasNext()) {
+            final String nextImsStr = imeSplitter.next();
+            imeSubtypeSplitter.setString(nextImsStr);
+            if (imeSubtypeSplitter.hasNext()) {
+                if (needsImeSeparator) {
+                    sb.append(INPUT_METHOD_SEPARATOR);
+                }
+                if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
+                    sb.append(imeId);
+                    for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
+                        sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
+                        sb.append(enabledSubtypeHashCodes.get(i));
+                    }
+                } else {
+                    sb.append(nextImsStr);
+                }
+                needsImeSeparator = true;
+            }
+        }
+        return sb.toString();
+    }
+
+    void dumpLocked(final Printer pw, final String prefix) {
+        pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4439b06..4305808 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -31,7 +31,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 58a68f2a..361cdbb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -28,15 +28,10 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build;
-import android.os.LocaleList;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Printer;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
@@ -51,10 +46,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 /**
  * This class provides random static utility methods for {@link InputMethodManagerService} and its
@@ -68,12 +61,11 @@
     public static final boolean DEBUG = false;
     static final int NOT_A_SUBTYPE_ID = -1;
     private static final String TAG = "InputMethodUtils";
-    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
 
     // The string for enabled input method is saved as follows:
     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
-    private static final char INPUT_METHOD_SEPARATOR = ':';
-    private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+    static final char INPUT_METHOD_SEPARATOR = ':';
+    static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
 
     private InputMethodUtils() {
         // This utility class is not publicly instantiable.
@@ -200,640 +192,6 @@
             UserHandle.getUserId(uid));
     }
 
-    /**
-     * Utility class for putting and getting settings for InputMethod.
-     *
-     * This is used in two ways:
-     * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to
-     * follow the current user.
-     * - On-demand instances when we need settings for non-current users.
-     *
-     * TODO: Move all putters and getters of settings to this class.
-     */
-    @UserHandleAware
-    public static class InputMethodSettings {
-        private final ArrayMap<String, InputMethodInfo> mMethodMap;
-
-        @UserIdInt
-        private final int mCurrentUserId;
-
-        private static void buildEnabledInputMethodsSettingString(
-                StringBuilder builder, Pair<String, ArrayList<String>> ime) {
-            builder.append(ime.first);
-            // Inputmethod and subtypes are saved in the settings as follows:
-            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
-            for (String subtypeId: ime.second) {
-                builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
-            }
-        }
-
-        InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
-            mMethodMap = methodMap;
-            mCurrentUserId = userId;
-            String ime = getSelectedInputMethod();
-            String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
-            if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
-                putSelectedInputMethod(defaultDeviceIme);
-                putSelectedDefaultDeviceInputMethod(null);
-            }
-        }
-
-        private void putString(@NonNull String key, @Nullable String str) {
-            SecureSettingsWrapper.putString(key, str, mCurrentUserId);
-        }
-
-        @Nullable
-        private String getString(@NonNull String key, @Nullable String defaultValue) {
-            return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
-        }
-
-        private void putInt(String key, int value) {
-            SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
-        }
-
-        private int getInt(String key, int defaultValue) {
-            return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
-        }
-
-        ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
-            return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
-        }
-
-        @NonNull
-        ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
-                @Nullable Predicate<InputMethodInfo> matchingCondition) {
-            return createEnabledInputMethodListLocked(
-                    getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
-        }
-
-        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
-                InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
-            List<InputMethodSubtype> enabledSubtypes =
-                    getEnabledInputMethodSubtypeListLocked(imi);
-            if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
-                enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                        SystemLocaleWrapper.get(mCurrentUserId), imi);
-            }
-            return InputMethodSubtype.sort(imi, enabledSubtypes);
-        }
-
-        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
-            List<Pair<String, ArrayList<String>>> imsList =
-                    getEnabledInputMethodsAndSubtypeListLocked();
-            ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
-            if (imi != null) {
-                for (Pair<String, ArrayList<String>> imsPair : imsList) {
-                    InputMethodInfo info = mMethodMap.get(imsPair.first);
-                    if (info != null && info.getId().equals(imi.getId())) {
-                        final int subtypeCount = info.getSubtypeCount();
-                        for (int i = 0; i < subtypeCount; ++i) {
-                            InputMethodSubtype ims = info.getSubtypeAt(i);
-                            for (String s: imsPair.second) {
-                                if (String.valueOf(ims.hashCode()).equals(s)) {
-                                    enabledSubtypes.add(ims);
-                                }
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-            return enabledSubtypes;
-        }
-
-        List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
-            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
-            final TextUtils.SimpleStringSplitter inputMethodSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter subtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-            final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
-            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
-                return imsList;
-            }
-            inputMethodSplitter.setString(enabledInputMethodsStr);
-            while (inputMethodSplitter.hasNext()) {
-                String nextImsStr = inputMethodSplitter.next();
-                subtypeSplitter.setString(nextImsStr);
-                if (subtypeSplitter.hasNext()) {
-                    ArrayList<String> subtypeHashes = new ArrayList<>();
-                    // The first element is ime id.
-                    String imeId = subtypeSplitter.next();
-                    while (subtypeSplitter.hasNext()) {
-                        subtypeHashes.add(subtypeSplitter.next());
-                    }
-                    imsList.add(new Pair<>(imeId, subtypeHashes));
-                }
-            }
-            return imsList;
-        }
-
-        /**
-         * Build and put a string of EnabledInputMethods with removing specified Id.
-         * @return the specified id was removed or not.
-         */
-        boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
-                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
-            boolean isRemoved = false;
-            boolean needsAppendSeparator = false;
-            for (Pair<String, ArrayList<String>> ims: imsList) {
-                String curId = ims.first;
-                if (curId.equals(id)) {
-                    // We are disabling this input method, and it is
-                    // currently enabled.  Skip it to remove from the
-                    // new list.
-                    isRemoved = true;
-                } else {
-                    if (needsAppendSeparator) {
-                        builder.append(INPUT_METHOD_SEPARATOR);
-                    } else {
-                        needsAppendSeparator = true;
-                    }
-                    buildEnabledInputMethodsSettingString(builder, ims);
-                }
-            }
-            if (isRemoved) {
-                // Update the setting with the new list of input methods.
-                putEnabledInputMethodsStr(builder.toString());
-            }
-            return isRemoved;
-        }
-
-        private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
-                List<Pair<String, ArrayList<String>>> imsList,
-                Predicate<InputMethodInfo> matchingCondition) {
-            final ArrayList<InputMethodInfo> res = new ArrayList<>();
-            for (Pair<String, ArrayList<String>> ims: imsList) {
-                InputMethodInfo info = mMethodMap.get(ims.first);
-                if (info != null && !info.isVrOnly()
-                        && (matchingCondition == null || matchingCondition.test(info))) {
-                    res.add(info);
-                }
-            }
-            return res;
-        }
-
-        void putEnabledInputMethodsStr(@Nullable String str) {
-            if (DEBUG) {
-                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
-            }
-            if (TextUtils.isEmpty(str)) {
-                // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
-                // empty data scenario.
-                putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
-            } else {
-                putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
-            }
-        }
-
-        @NonNull
-        String getEnabledInputMethodsStr() {
-            return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
-        }
-
-        private void saveSubtypeHistory(
-                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
-            StringBuilder builder = new StringBuilder();
-            boolean isImeAdded = false;
-            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
-                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
-                        newSubtypeId);
-                isImeAdded = true;
-            }
-            for (Pair<String, String> ime: savedImes) {
-                String imeId = ime.first;
-                String subtypeId = ime.second;
-                if (TextUtils.isEmpty(subtypeId)) {
-                    subtypeId = NOT_A_SUBTYPE_ID_STR;
-                }
-                if (isImeAdded) {
-                    builder.append(INPUT_METHOD_SEPARATOR);
-                } else {
-                    isImeAdded = true;
-                }
-                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
-                        subtypeId);
-            }
-            // Remove the last INPUT_METHOD_SEPARATOR
-            putSubtypeHistoryStr(builder.toString());
-        }
-
-        private void addSubtypeToHistory(String imeId, String subtypeId) {
-            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-            for (Pair<String, String> ime: subtypeHistory) {
-                if (ime.first.equals(imeId)) {
-                    if (DEBUG) {
-                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
-                                + ime.second);
-                    }
-                    // We should break here
-                    subtypeHistory.remove(ime);
-                    break;
-                }
-            }
-            if (DEBUG) {
-                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
-            }
-            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
-        }
-
-        private void putSubtypeHistoryStr(@NonNull String str) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
-            }
-            if (TextUtils.isEmpty(str)) {
-                // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
-                // data scenario.
-                putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
-            } else {
-                putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
-            }
-        }
-
-        Pair<String, String> getLastInputMethodAndSubtypeLocked() {
-            // Gets the first one from the history
-            return getLastSubtypeForInputMethodLockedInternal(null);
-        }
-
-        @Nullable
-        InputMethodSubtype getLastInputMethodSubtypeLocked() {
-            final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
-            // TODO: Handle the case of the last IME with no subtypes
-            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
-                    || TextUtils.isEmpty(lastIme.second)) return null;
-            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
-            if (lastImi == null) return null;
-            try {
-                final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-                final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
-                        lastSubtypeHash);
-                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
-                    return null;
-                }
-                return lastImi.getSubtypeAt(lastSubtypeId);
-            } catch (NumberFormatException e) {
-                return null;
-            }
-        }
-
-        String getLastSubtypeForInputMethodLocked(String imeId) {
-            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
-            if (ime != null) {
-                return ime.second;
-            } else {
-                return null;
-            }
-        }
-
-        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
-            List<Pair<String, ArrayList<String>>> enabledImes =
-                    getEnabledInputMethodsAndSubtypeListLocked();
-            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
-                final String imeInTheHistory = imeAndSubtype.first;
-                // If imeId is empty, returns the first IME and subtype in the history
-                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
-                    final String subtypeInTheHistory = imeAndSubtype.second;
-                    final String subtypeHashCode =
-                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
-                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
-                    if (!TextUtils.isEmpty(subtypeHashCode)) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
-                        }
-                        return new Pair<>(imeInTheHistory, subtypeHashCode);
-                    }
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "No enabled IME found in the history");
-            }
-            return null;
-        }
-
-        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
-                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
-            final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
-            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
-                if (enabledIme.first.equals(imeId)) {
-                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
-                    final InputMethodInfo imi = mMethodMap.get(imeId);
-                    if (explicitlyEnabledSubtypes.size() == 0) {
-                        // If there are no explicitly enabled subtypes, applicable subtypes are
-                        // enabled implicitly.
-                        // If IME is enabled and no subtypes are enabled, applicable subtypes
-                        // are enabled implicitly, so needs to treat them to be enabled.
-                        if (imi != null && imi.getSubtypeCount() > 0) {
-                            List<InputMethodSubtype> implicitlyEnabledSubtypes =
-                                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
-                                            imi);
-                            final int numSubtypes = implicitlyEnabledSubtypes.size();
-                            for (int i = 0; i < numSubtypes; ++i) {
-                                final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
-                                if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
-                                    return subtypeHashCode;
-                                }
-                            }
-                        }
-                    } else {
-                        for (String s: explicitlyEnabledSubtypes) {
-                            if (s.equals(subtypeHashCode)) {
-                                // If both imeId and subtypeId are enabled, return subtypeId.
-                                try {
-                                    final int hashCode = Integer.parseInt(subtypeHashCode);
-                                    // Check whether the subtype id is valid or not
-                                    if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
-                                        return s;
-                                    } else {
-                                        return NOT_A_SUBTYPE_ID_STR;
-                                    }
-                                } catch (NumberFormatException e) {
-                                    return NOT_A_SUBTYPE_ID_STR;
-                                }
-                            }
-                        }
-                    }
-                    // If imeId was enabled but subtypeId was disabled.
-                    return NOT_A_SUBTYPE_ID_STR;
-                }
-            }
-            // If both imeId and subtypeId are disabled, return null
-            return null;
-        }
-
-        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
-            ArrayList<Pair<String, String>> imsList = new ArrayList<>();
-            final String subtypeHistoryStr = getSubtypeHistoryStr();
-            if (TextUtils.isEmpty(subtypeHistoryStr)) {
-                return imsList;
-            }
-            final TextUtils.SimpleStringSplitter inputMethodSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter subtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-            inputMethodSplitter.setString(subtypeHistoryStr);
-            while (inputMethodSplitter.hasNext()) {
-                String nextImsStr = inputMethodSplitter.next();
-                subtypeSplitter.setString(nextImsStr);
-                if (subtypeSplitter.hasNext()) {
-                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
-                    // The first element is ime id.
-                    String imeId = subtypeSplitter.next();
-                    while (subtypeSplitter.hasNext()) {
-                        subtypeId = subtypeSplitter.next();
-                        break;
-                    }
-                    imsList.add(new Pair<>(imeId, subtypeId));
-                }
-            }
-            return imsList;
-        }
-
-        @NonNull
-        private String getSubtypeHistoryStr() {
-            final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
-            if (DEBUG) {
-                Slog.d(TAG, "getSubtypeHistoryStr: " + history);
-            }
-            return history;
-        }
-
-        void putSelectedInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
-                        + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
-        }
-
-        void putSelectedSubtype(int subtypeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
-                        + mCurrentUserId);
-            }
-            putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
-        }
-
-        @Nullable
-        String getSelectedInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
-            }
-            return imi;
-        }
-
-        @Nullable
-        String getSelectedDefaultDeviceInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
-                        + mCurrentUserId);
-            }
-            return imi;
-        }
-
-        void putSelectedDefaultDeviceInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
-                        + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
-        }
-
-        void putDefaultVoiceInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
-        }
-
-        @Nullable
-        String getDefaultVoiceInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
-            }
-            return imi;
-        }
-
-        boolean isSubtypeSelected() {
-            return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
-        }
-
-        private int getSelectedInputMethodSubtypeHashCode() {
-            return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
-        }
-
-        @UserIdInt
-        public int getCurrentUserId() {
-            return mCurrentUserId;
-        }
-
-        int getSelectedInputMethodSubtypeId(String selectedImiId) {
-            final InputMethodInfo imi = mMethodMap.get(selectedImiId);
-            if (imi == null) {
-                return NOT_A_SUBTYPE_ID;
-            }
-            final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-            return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
-        }
-
-        void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
-                InputMethodSubtype currentSubtype) {
-            String subtypeId = NOT_A_SUBTYPE_ID_STR;
-            if (currentSubtype != null) {
-                subtypeId = String.valueOf(currentSubtype.hashCode());
-            }
-            if (canAddToLastInputMethod(currentSubtype)) {
-                addSubtypeToHistory(curMethodId, subtypeId);
-            }
-        }
-
-        /**
-         * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
-         * non-current users.
-         *
-         * <p>TODO: Address code duplication between this and
-         * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
-         *
-         * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
-         */
-        @Nullable
-        InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
-            final String selectedMethodId = getSelectedInputMethod();
-            if (selectedMethodId == null) {
-                return null;
-            }
-            final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
-            if (imi == null || imi.getSubtypeCount() == 0) {
-                return null;
-            }
-
-            final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-            if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) {
-                final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
-                        subtypeHashCode);
-                if (subtypeIndex >= 0) {
-                    return imi.getSubtypeAt(subtypeIndex);
-                }
-            }
-
-            // If there are no selected subtypes, the framework will try to find the most applicable
-            // subtype from explicitly or implicitly enabled subtypes.
-            final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                    getEnabledInputMethodSubtypeListLocked(imi, true);
-            // If there is only one explicitly or implicitly enabled subtype, just returns it.
-            if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
-                return null;
-            }
-            if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
-                return explicitlyOrImplicitlyEnabledSubtypes.get(0);
-            }
-            final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
-            final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                    explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
-                    locale, true);
-            if (subtype != null) {
-                return subtype;
-            }
-            return SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                    explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
-        }
-
-        boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
-                @NonNull ArrayList<InputMethodSubtype> subtypes,
-                @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
-                @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
-            final InputMethodInfo imi = mMethodMap.get(imeId);
-            if (imi == null) {
-                return false;
-            }
-            if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
-                    imi.getPackageName())) {
-                return false;
-            }
-
-            if (subtypes.isEmpty()) {
-                additionalSubtypeMap.remove(imi.getId());
-            } else {
-                additionalSubtypeMap.put(imi.getId(), subtypes);
-            }
-            AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
-            return true;
-        }
-
-        boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
-                @NonNull int[] subtypeHashCodes) {
-            final InputMethodInfo imi = mMethodMap.get(imeId);
-            if (imi == null) {
-                return false;
-            }
-
-            final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
-            for (int subtypeHashCode : subtypeHashCodes) {
-                if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
-                    continue;  // NOT_A_SUBTYPE_ID must not be saved
-                }
-                if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
-                    continue;  // this subtype does not exist in InputMethodInfo.
-                }
-                if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
-                    continue;  // The entry is already added.  No need to add anymore.
-                }
-                validSubtypeHashCodes.add(subtypeHashCode);
-            }
-
-            final String originalEnabledImesString = getEnabledInputMethodsStr();
-            final String updatedEnabledImesString = updateEnabledImeString(
-                    originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
-            if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
-                return false;
-            }
-
-            putEnabledInputMethodsStr(updatedEnabledImesString);
-            return true;
-        }
-
-        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-        static String updateEnabledImeString(@NonNull String enabledImesString,
-                @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
-            final TextUtils.SimpleStringSplitter imeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
-            final StringBuilder sb = new StringBuilder();
-
-            imeSplitter.setString(enabledImesString);
-            boolean needsImeSeparator = false;
-            while (imeSplitter.hasNext()) {
-                final String nextImsStr = imeSplitter.next();
-                imeSubtypeSplitter.setString(nextImsStr);
-                if (imeSubtypeSplitter.hasNext()) {
-                    if (needsImeSeparator) {
-                        sb.append(INPUT_METHOD_SEPARATOR);
-                    }
-                    if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
-                        sb.append(imeId);
-                        for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
-                            sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
-                            sb.append(enabledSubtypeHashCodes.get(i));
-                        }
-                    } else {
-                        sb.append(nextImsStr);
-                    }
-                    needsImeSeparator = true;
-                }
-            }
-            return sb.toString();
-        }
-
-        public void dumpLocked(final Printer pw, final String prefix) {
-            pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
-        }
-    }
-
     static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
             @StartInputFlags int startInputFlags) {
         if (targetSdkVersion < Build.VERSION_CODES.P) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index 1b9e6fb..a8eace0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -153,6 +153,15 @@
     }
 
     @Test
+    public void testGetIsHardwareIgnoringTouches() throws RemoteException {
+        mListener.onHardwareIgnoreTouchesChanged(true);
+        assertThat(mProvider.isHardwareIgnoringTouches()).isTrue();
+
+        mListener.onHardwareIgnoreTouchesChanged(false);
+        assertThat(mProvider.isHardwareIgnoringTouches()).isFalse();
+    }
+
+    @Test
     public void testGetDockedState() {
         final List<Integer> states = List.of(Intent.EXTRA_DOCK_STATE_DESK,
                 Intent.EXTRA_DOCK_STATE_CAR, Intent.EXTRA_DOCK_STATE_UNDOCKED);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
index 5cff48d..4119352 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.OperationContext;
@@ -48,11 +49,13 @@
     public void testConvertsWakeReason_whenPowerReason() {
         final OperationContext context = new OperationContext();
         context.wakeReason = WakeReason.WAKE_MOTION;
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
-                .toProtoWakeReasonDetails(new OperationContextExt(context, false));
+                .toProtoWakeReasonDetails(
+                        new OperationContextExt(context, false, BiometricAuthenticator.TYPE_NONE));
 
         assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
         assertThat(reasonDetails).isEmpty();
@@ -63,7 +66,8 @@
         final OperationContext context = new OperationContext();
         context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
                 AuthenticateReason.Face.ASSISTANT_VISIBLE);
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -79,7 +83,8 @@
         final OperationContext context = new OperationContext();
         context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
                 new AuthenticateReason.Vendor());
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -96,7 +101,8 @@
         context.wakeReason = WakeReason.WAKE_KEY;
         context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
                 AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -113,7 +119,8 @@
         context.wakeReason = WakeReason.LID;
         context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
                 new AuthenticateReason.Vendor());
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index 32284fd..767b426 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -18,17 +18,19 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.content.Intent;
 import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.DisplayState;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
 
-import static org.mockito.Mockito.when;
-
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.InstanceId;
@@ -58,7 +60,7 @@
 
         final OperationContext aidlContext = newAidlContext();
 
-        context = new OperationContextExt(aidlContext, false);
+        context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
         assertThat(context.toAidlContext()).isSameInstanceAs(aidlContext);
 
         final int id = 5;
@@ -96,7 +98,8 @@
         );
 
         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
-            final OperationContextExt context = new OperationContextExt(newAidlContext(), true);
+            final OperationContextExt context = new OperationContextExt(newAidlContext(), true,
+                    BiometricAuthenticator.TYPE_NONE);
             when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
             assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState())
                     .isEqualTo(entry.getValue());
@@ -124,7 +127,7 @@
         updatesFromSource(null, OperationReason.UNKNOWN);
     }
 
-    private  void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
+    private void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
         final int rotation = Surface.ROTATION_270;
         final int foldState = IBiometricContextListener.FoldState.HALF_OPENED;
         final int dockState = Intent.EXTRA_DOCK_STATE_CAR;
@@ -135,9 +138,11 @@
         when(mBiometricContext.getDockedState()).thenReturn(dockState);
         when(mBiometricContext.isDisplayOn()).thenReturn(true);
         when(mBiometricContext.getDisplayState()).thenReturn(displayState);
+        when(mBiometricContext.isHardwareIgnoringTouches()).thenReturn(true);
 
         final OperationContextExt context = new OperationContextExt(newAidlContext(),
-                sessionType == OperationReason.BIOMETRIC_PROMPT);
+                sessionType == OperationReason.BIOMETRIC_PROMPT,
+                BiometricAuthenticator.TYPE_FINGERPRINT);
 
         assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context);
 
@@ -154,6 +159,46 @@
         assertThat(context.getOrientation()).isEqualTo(rotation);
         assertThat(context.isDisplayOn()).isTrue();
         assertThat(context.getDisplayState()).isEqualTo(DisplayState.AOD);
+        assertThat(
+            context.getOperationState().getFingerprintOperationState().isHardwareIgnoringTouches
+        ).isTrue();
+    }
+
+    @Test
+    public void hasNullOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
+        assertThat(context.getOperationState()).isNull();
+    }
+
+    @Test
+    public void hasFaceOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false,
+                BiometricAuthenticator.TYPE_FACE);
+        assertThat(context.getOperationState().getTag()).isEqualTo(
+                OperationState.faceOperationState);
+    }
+
+    @Test
+    public void hasFingerprintOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false,
+                BiometricAuthenticator.TYPE_FINGERPRINT);
+        assertThat(context.getOperationState().getTag()).isEqualTo(
+                OperationState.fingerprintOperationState);
     }
 
     private static OperationContext newAidlContext() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 2d9d868..4604b31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -72,6 +72,7 @@
                 mToken, mClientCallback);
         client.start(mSchedulerCallback);
         assertTrue(client.mHalOperationRunning);
+        verify(mClientCallback).getModality();
         verify(mSchedulerCallback).onClientStarted(eq(client));
 
         // Pretend that it got canceled by the user.
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
new file mode 100644
index 0000000..a55d1c4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+
+import android.text.TextUtils;
+import android.util.IntArray;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class InputMethodSettingsTest {
+    private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
+            @NonNull String initialEnabledImeStr, @NonNull String imeId,
+            @NonNull String enabledSubtypeHashCodesStr) {
+        assertEquals(expectedEnabledImeStr,
+                InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
+                        imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
+    }
+
+    private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
+        final IntArray subtypes = new IntArray();
+        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                new TextUtils.SimpleStringSplitter(';');
+        if (TextUtils.isEmpty(subtypeHashCodesStr)) {
+            return subtypes;
+        }
+        imeSubtypeSplitter.setString(subtypeHashCodesStr);
+        while (imeSubtypeSplitter.hasNext()) {
+            subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
+        }
+        return subtypes;
+    }
+
+    @Test
+    public void updateEnabledImeStringTest() {
+        // No change cases
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+
+        // To enable subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1",
+                "com.android/.ime1", "com.android/.ime1", "1");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3",
+                "com.android/.ime1", "com.android/.ime1", "1;2;3");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3",
+                "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
+                "1;2;3");
+
+        // To reset enabled subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1:com.android/.ime2",
+                "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1",
+                "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
+                "");
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 95a9610..9688ef6 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -34,9 +34,7 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
-import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.IntArray;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1211,28 +1209,6 @@
                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
     }
 
-    private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
-        final IntArray subtypes = new IntArray();
-        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
-                new TextUtils.SimpleStringSplitter(';');
-        if (TextUtils.isEmpty(subtypeHashCodesStr)) {
-            return subtypes;
-        }
-        imeSubtypeSplitter.setString(subtypeHashCodesStr);
-        while (imeSubtypeSplitter.hasNext()) {
-            subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
-        }
-        return subtypes;
-    }
-
-    private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
-            @NonNull String initialEnabledImeStr, @NonNull String imeId,
-            @NonNull String enabledSubtypeHashCodesStr) {
-        assertEquals(expectedEnabledImeStr,
-                InputMethodUtils.InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
-                        imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
-    }
-
     private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
             @NonNull String... expected) {
         final ArrayList<String> actual = new ArrayList<>();
@@ -1280,57 +1256,4 @@
                         "com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
                 .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
     }
-
-    @Test
-    public void updateEnabledImeStringTest() {
-        // No change cases
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime2", "");
-
-        // To enable subtypes
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime2", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1",
-                "com.android/.ime1", "com.android/.ime1", "1");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1;2;3",
-                "com.android/.ime1", "com.android/.ime1", "1;2;3");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1;2;3:com.android/.ime2",
-                "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1;1;2;3",
-                "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
-                "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
-                "1;2;3");
-
-        // To reset enabled subtypes
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1;1", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1;1;2;3", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1:com.android/.ime2",
-                "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1",
-                "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1:com.android/.ime2",
-                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
-                "");
-    }
 }