Merge "Rate limit calls to AM.getMyMemoryState()." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index df4a3e5..bd17d6d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -20,6 +20,7 @@
     java_aconfig_libraries: [
         // !!! KEEP THIS LIST ALPHABETICAL !!!
         "aconfig_mediacodec_flags_java_lib",
+        "android-sdk-flags-java",
         "android.adaptiveauth.flags-aconfig-java",
         "android.app.appfunctions.flags-aconfig-java",
         "android.app.contextualsearch.flags-aconfig-java",
diff --git a/Android.bp b/Android.bp
index f8907f3..258440f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -98,6 +98,7 @@
         ":android.frameworks.location.altitude-V2-java-source",
         ":android.hardware.biometrics.common-V4-java-source",
         ":android.hardware.biometrics.fingerprint-V5-java-source",
+        ":android.hardware.biometrics.fingerprint.virtualhal-java-source",
         ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
diff --git a/BROADCASTS_OWNERS b/BROADCASTS_OWNERS
index 01f1f8a..f0cbe46 100644
--- a/BROADCASTS_OWNERS
+++ b/BROADCASTS_OWNERS
@@ -1,5 +1,5 @@
 # Bug component: 316181
-ctate@android.com
-jsharkey@google.com
+set noparent
+
 sudheersai@google.com
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/android-sdk-flags/Android.bp b/android-sdk-flags/Android.bp
new file mode 100644
index 0000000..79a0b9a
--- /dev/null
+++ b/android-sdk-flags/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+aconfig_declarations {
+    name: "android-sdk-flags",
+    package: "android.sdk",
+    container: "system",
+    srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android-sdk-flags-java",
+    aconfig_declarations: "android-sdk-flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
new file mode 100644
index 0000000..cfe298e
--- /dev/null
+++ b/android-sdk-flags/flags.aconfig
@@ -0,0 +1,12 @@
+package: "android.sdk"
+container: "system"
+
+flag {
+    name: "major_minor_versioning_scheme"
+    namespace: "android_sdk"
+    description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
+    bug: "350458259"
+
+    # Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
+    is_fixed_read_only: true
+}
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
index a53bbea..676cbc7 100644
--- a/apex/blobstore/OWNERS
+++ b/apex/blobstore/OWNERS
@@ -1,2 +1,5 @@
+# Bug component: 25692
+set noparent
+
 sudheersai@google.com
-yamasani@google.com
+yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index 52670a2..dd0d1b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -10,14 +10,10 @@
       ]
     },
     {
-      "name": "CtsBRSTestCases",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "name": "CtsBRSTestCases"
     },
     {
-      "name": "FrameworksServicesTests_com_android_server_usage_Presubmit"
+      "name": "FrameworksServicesTests_com_android_server_usage"
     }
   ],
   "postsubmit": [
diff --git a/core/api/current.txt b/core/api/current.txt
index 5e8febe..9875c02 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32771,6 +32771,7 @@
     field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
     field @Deprecated public static final String SDK;
     field public static final int SDK_INT;
+    field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_MINOR_INT;
     field public static final String SECURITY_PATCH;
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 60dc52b..fb425a9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10561,6 +10561,7 @@
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean shouldDefaultToObserveMode();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
+    field @FlaggedApi("android.permission.flags.wallet_role_icon_property_enabled") public static final String PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL = "android.nfc.cardemulation.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL";
   }
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
@@ -18155,9 +18156,17 @@
     field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
   }
 
+  @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public static class WindowManager.InsetsParams {
+    ctor public WindowManager.InsetsParams(int);
+    method @Nullable public android.graphics.Insets getInsetsSize();
+    method public int getType();
+    method @NonNull public android.view.WindowManager.InsetsParams setInsetsSize(@Nullable android.graphics.Insets);
+  }
+
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
     method public final long getUserActivityTimeout();
     method public boolean isSystemApplicationOverlay();
+    method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>);
     method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
     method public final void setUserActivityTimeout(long);
     field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7a36fbb..7a33ab7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1593,6 +1593,17 @@
      */
     @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
     public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText";
+
+    /**
+     * {@link #extras} key: {@link Icon} of an image used as a thumb icon on
+     * {@link Notification} progress bar for {@link EnRouteStyle} notifications.
+     * This extra is an {@code Icon}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public static final String EXTRA_ENROUTE_PROGRESS_THUMB_ICON =
+            "android.enrouteProgressThumbIcon";
+
     /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}.
@@ -3052,6 +3063,8 @@
 
         if (Flags.apiRichOngoing()) {
             visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
+            visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON,
+                Icon.class));
         }
 
         if (mBubbleMetadata != null) {
@@ -11015,6 +11028,9 @@
         @Nullable
         private CharSequence mLargeIconSubText = null;
 
+        @Nullable
+        private Icon mProgressThumbIcon = null;
+
         public EnRouteStyle() {
         }
 
@@ -11058,6 +11074,25 @@
             return this;
         }
 
+        /**
+         * Returns the progress thumb icon.
+         * @see EnRouteStyle#setProgressThumbIcon
+         */
+        @Nullable
+        public Icon getProgressThumbIcon() {
+            return mProgressThumbIcon;
+        }
+
+        /**
+         * Optional icon to be used as a progress thumb.
+         */
+        @NonNull
+        public EnRouteStyle setProgressThumbIcon(@Nullable Icon progressThumbIcon) {
+            mProgressThumbIcon = progressThumbIcon;
+            return this;
+        }
+
+
          /**
          * @hide
          */
@@ -11069,7 +11104,8 @@
 
             final EnRouteStyle enRouteStyle = (EnRouteStyle) other;
             return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon)
-                    || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText);
+                    || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText)
+                    || !Objects.equals(mProgressThumbIcon, enRouteStyle.mProgressThumbIcon);
         }
 
         /**
@@ -11080,6 +11116,7 @@
             super.addExtras(extras);
             extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon);
             extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText);
+            extras.putParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON, mProgressThumbIcon);
         }
 
         /**
@@ -11090,6 +11127,8 @@
             super.restoreFromExtras(extras);
             mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class);
             mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT);
+            mProgressThumbIcon =
+                    extras.getParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON, Icon.class);
         }
 
         /**
@@ -11101,6 +11140,10 @@
             if (mOverlayIcon != null) {
                 mOverlayIcon.convertToAshmem();
             }
+
+            if (mProgressThumbIcon != null) {
+                mProgressThumbIcon.convertToAshmem();
+            }
         }
 
         /**
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1b29b7a..32e9542 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -761,14 +761,22 @@
         this.mVibrationEnabled = effect != null;
         this.mVibrationEffect = effect;
         if (Flags.notifChannelCropVibrationEffects() && effect != null) {
-            // Try converting to a vibration pattern and trimming that array. If not convertible
-            // to a pattern directly, try trimming the vibration effect if possible and storing
-            // that version instead.
             long[] pattern = effect.computeCreateWaveformOffOnTimingsOrNull();
             if (pattern != null) {
-                setVibrationPattern(pattern);
+                // If this effect has an equivalent pattern, AND the pattern needs to be truncated
+                // due to being too long, we delegate to setVibrationPattern to re-generate the
+                // effect as well. Otherwise, we use the effect (already set above) and converted
+                // pattern directly.
+                if (pattern.length > MAX_VIBRATION_LENGTH) {
+                    setVibrationPattern(pattern);
+                } else {
+                    this.mVibrationPattern = pattern;
+                }
             } else {
+                // If not convertible to a pattern directly, try trimming the vibration effect if
+                // possible and storing that version instead.
                 this.mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect);
+                this.mVibrationPattern = null;
             }
         } else {
             this.mVibrationPattern =
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 8e08a95..081dfe6 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -152,6 +152,16 @@
 }
 
 flag {
+  name: "fix_race_condition_in_tie_profile_lock"
+  namespace: "enterprise"
+  description: "Fix race condition in tieProfileLockIfNecessary()"
+  bug: "355905501"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "quiet_mode_credential_bug_fix"
   namespace: "enterprise"
   description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index c4bfae9..f5c5a11 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -61,6 +61,20 @@
         return RUNTIME_SCHEMA_TYPE + RUNTIME_SCHEMA_TYPE_SEPARATOR + Objects.requireNonNull(pkg);
     }
 
+    /** Returns the package name from the runtime metadata schema name. */
+    @NonNull
+    public static String getPackageNameFromSchema(String metadataSchemaType) {
+        String[] split = metadataSchemaType.split(RUNTIME_SCHEMA_TYPE_SEPARATOR);
+        if (split.length > 2) {
+            throw new IllegalArgumentException(
+                    "Invalid schema type: " + metadataSchemaType + " for app function runtime");
+        }
+        if (split.length < 2) {
+            return APP_FUNCTION_INDEXER_PACKAGE;
+        }
+        return split[1];
+    }
+
     /** Returns the document id for an app function's runtime metadata. */
     public static String getDocumentIdForAppFunction(
             @NonNull String pkg, @NonNull String functionId) {
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
index 926cc9a..085e0a4 100644
--- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -39,6 +39,8 @@
     public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
 
     public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
+    public static final String PROPERTY_FUNCTION_ID = "functionId";
+    public static final String PROPERTY_PACKAGE_NAME = "packageName";
 
     // These are constants that has to be kept the same with {@code
     // com.android.server.appsearch.appsindexer.appsearchtypes.AppSearchHelper}.
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 748260b..e9fa3e1 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -43,6 +43,7 @@
     name: "virtual_display_insets"
     description: "APIs for specifying virtual display insets (via cutout)"
     bug: "350007135"
+    is_exported: true
 }
 
 flag {
@@ -88,6 +89,7 @@
     name: "virtual_display_rotation_api"
     description: "API for on-demand rotation of virtual displays"
     bug: "291748430"
+    is_exported: true
 }
 
 flag {
@@ -110,6 +112,7 @@
     name: "device_aware_display_power"
     description: "Device awareness in power and display APIs"
     bug: "285020111"
+    is_exported: true
 }
 
 flag {
@@ -125,4 +128,12 @@
     namespace: "virtual_devices"
     description: "Allow for status bar and insets on virtual devices"
     bug: "350007866"
+    is_exported: true
+}
+
+flag {
+  namespace: "virtual_devices"
+  name: "camera_timestamp_from_surface"
+  description: "Pass the surface timestamp to the capture result"
+  bug: "351341245"
 }
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index cb3455b..bb91a37 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -43,9 +43,11 @@
     private final PackageManager mPm;
     private final LauncherActivityInfoInternal mInternal;
 
-    private static final UnicodeSet TRIMMABLE_CHARACTERS =
+    private static final UnicodeSet INVISIBLE_CHARACTERS =
             new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]",
                     /* ignoreWhitespace= */ false).freeze();
+    // Only allow 3 consecutive invisible characters in the prefix of the string.
+    private static final int PREFIX_CONSECUTIVE_INVISIBLE_CHARACTERS_MAXIMUM = 3;
 
     /**
      * Create a launchable activity object for a given ResolveInfo and user.
@@ -93,17 +95,21 @@
             return getActivityInfo().loadLabel(mPm);
         }
 
-        CharSequence label = trim(getActivityInfo().loadLabel(mPm));
-        // If the trimmed label is empty, use application's label instead
-        if (TextUtils.isEmpty(label)) {
-            label = trim(getApplicationInfo().loadLabel(mPm));
-            // If the trimmed label is still empty, use package name instead
-            if (TextUtils.isEmpty(label)) {
-                label = getComponentName().getPackageName();
-            }
+        CharSequence label = getActivityInfo().loadLabel(mPm).toString().trim();
+        // If the activity label is visible to the user, return the original activity label
+        if (isVisible(label)) {
+            return label;
         }
-        // TODO: Go through LauncherAppsService
-        return label;
+
+        // Use application label instead
+        label = getApplicationInfo().loadLabel(mPm).toString().trim();
+        // If the application label is visible to the user, return the original application label
+        if (isVisible(label)) {
+            return label;
+        }
+
+        // Use package name instead
+        return getComponentName().getPackageName();
     }
 
     /**
@@ -207,147 +213,75 @@
     }
 
     /**
-     * If the {@code ch} is trimmable, return {@code true}. Otherwise, return
-     * {@code false}. If the count of the code points of {@code ch} doesn't
-     * equal 1, return {@code false}.
+     * Check whether the {@code sequence} is visible to the user or not.
      * <p>
-     * There are two types of the trimmable characters.
-     * 1. The character is one of the Default_Ignorable_Code_Point in
+     * Return {@code false} when one of these conditions are satisfied:
+     * 1. The {@code sequence} starts with at least consecutive three invisible characters.
+     * 2. The sequence is composed of the invisible characters and non-glyph characters.
+     * <p>
+     * Invisible character is one of the Default_Ignorable_Code_Point in
      * <a href="
      * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt">
      * DerivedCoreProperties.txt</a>, the White_Space in <a href=
      * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt
      * </a> or category Cc.
      * <p>
-     * 2. The character is not supported in the current system font.
+     * Non-glyph character means the character is not supported in the current system font.
      * {@link android.graphics.Paint#hasGlyph(String)}
      * <p>
      *
+     * @hide
      */
-    private static boolean isTrimmable(@NonNull Paint paint, @NonNull CharSequence ch) {
-        Objects.requireNonNull(paint);
-        Objects.requireNonNull(ch);
-
-        // if ch is empty or it is not a character (i,e, the count of code
-        // point doesn't equal one), return false
-        if (TextUtils.isEmpty(ch)
-                || Character.codePointCount(ch, /* beginIndex= */ 0, ch.length()) != 1) {
+    @VisibleForTesting
+    public static boolean isVisible(@NonNull CharSequence sequence) {
+        Objects.requireNonNull(sequence);
+        if (TextUtils.isEmpty(sequence)) {
             return false;
         }
 
-        // Return true for the cases as below:
-        // 1. The character is in the TRIMMABLE_CHARACTERS set
-        // 2. The character is not supported in the system font
-        return TRIMMABLE_CHARACTERS.contains(ch) || !paint.hasGlyph(ch.toString());
-    }
-
-    /**
-     * If the {@code sequence} has some leading trimmable characters, creates a new copy
-     * and removes the trimmable characters from the copy. Otherwise the given
-     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
-     * to determine whether the character is trimmable or not.
-     *
-     * @return the trimmed string or the original string that has no
-     *         leading trimmable characters.
-     * @see    #isTrimmable(Paint, CharSequence)
-     * @see    #trim(CharSequence)
-     * @see    #trimEnd(CharSequence)
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    @NonNull
-    public static CharSequence trimStart(@NonNull CharSequence sequence) {
-        Objects.requireNonNull(sequence);
-
-        if (TextUtils.isEmpty(sequence)) {
-            return sequence;
-        }
-
         final Paint paint = new Paint();
-        int trimCount = 0;
+        int invisibleCharCount = 0;
+        int notSupportedCharCount = 0;
         final int[] codePoints = sequence.codePoints().toArray();
         for (int i = 0, length = codePoints.length; i < length; i++) {
             String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1);
-            if (!isTrimmable(paint, ch)) {
-                break;
+
+            // The check steps:
+            // 1. If the character is contained in INVISIBLE_CHARACTERS, invisibleCharCount++.
+            //    1.1 Check whether the invisibleCharCount is larger or equal to
+            //        PREFIX_INVISIBLE_CHARACTERS_MAXIMUM when notSupportedCharCount is zero.
+            //        It means that there are three consecutive invisible characters at the
+            //        start of the string, return false.
+            //    Otherwise, continue.
+            // 2. If the character is not supported on the system:
+            //    notSupportedCharCount++, continue
+            // 3. If it does not continue or return on the above two cases, it means the
+            //    character is visible and supported on the system, break.
+            // After going through the whole string, if the sum of invisibleCharCount
+            // and notSupportedCharCount is smaller than the length of the string, it
+            // means the string has the other visible characters, return true.
+            // Otherwise, return false.
+            if (INVISIBLE_CHARACTERS.contains(ch)) {
+                invisibleCharCount++;
+                // If there are three successive invisible characters at the start of the
+                // string, it is hard to visible to the user.
+                if (notSupportedCharCount == 0
+                        && invisibleCharCount >= PREFIX_CONSECUTIVE_INVISIBLE_CHARACTERS_MAXIMUM) {
+                    return false;
+                }
+                continue;
             }
-            trimCount += ch.length();
-        }
-        if (trimCount == 0) {
-            return sequence;
-        }
-        return sequence.subSequence(trimCount, sequence.length());
-    }
 
-    /**
-     * If the {@code sequence} has some trailing trimmable characters, creates a new copy
-     * and removes the trimmable characters from the copy. Otherwise the given
-     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
-     * to determine whether the character is trimmable or not.
-     *
-     * @return the trimmed sequence or the original sequence that has no
-     *         trailing trimmable characters.
-     * @see    #isTrimmable(Paint, CharSequence)
-     * @see    #trimStart(CharSequence)
-     * @see    #trim(CharSequence)
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    @NonNull
-    public static CharSequence trimEnd(@NonNull CharSequence sequence) {
-        Objects.requireNonNull(sequence);
-
-        if (TextUtils.isEmpty(sequence)) {
-            return sequence;
-        }
-
-        final Paint paint = new Paint();
-        int trimCount = 0;
-        final int[] codePoints = sequence.codePoints().toArray();
-        for (int i = codePoints.length - 1; i >= 0; i--) {
-            String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1);
-            if (!isTrimmable(paint, ch)) {
-                break;
+            // The character is not supported on the system, but it may not be an invisible
+            // character. E.g. tofu (a rectangle).
+            if (!paint.hasGlyph(ch)) {
+                notSupportedCharCount++;
+                continue;
             }
-            trimCount += ch.length();
+            // The character is visible and supported on the system, break the for loop
+            break;
         }
 
-        if (trimCount == 0) {
-            return sequence;
-        }
-        return sequence.subSequence(0, sequence.length() - trimCount);
-    }
-
-    /**
-     * If the {@code sequence} has some leading or trailing trimmable characters, creates
-     * a new copy and removes the trimmable characters from the copy. Otherwise the given
-     * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
-     * to determine whether the character is trimmable or not.
-     *
-     * @return the trimmed sequence or the original sequence that has no leading or
-     *         trailing trimmable characters.
-     * @see    #isTrimmable(Paint, CharSequence)
-     * @see    #trimStart(CharSequence)
-     * @see    #trimEnd(CharSequence)
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    @NonNull
-    public static CharSequence trim(@NonNull CharSequence sequence) {
-        Objects.requireNonNull(sequence);
-
-        if (TextUtils.isEmpty(sequence)) {
-            return sequence;
-        }
-
-        CharSequence result = trimStart(sequence);
-        if (TextUtils.isEmpty(result)) {
-            return result;
-        }
-
-        return trimEnd(result);
+        return (invisibleCharCount + notSupportedCharCount < codePoints.length);
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8c56a9d..26bb6e4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8847,6 +8847,8 @@
         } catch (PackageParserException e) {
             Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
             return null;
+        } finally {
+            parser2.close();
         }
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 8975191..9355937 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -170,6 +170,12 @@
     int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = 20;
 
     /**
+     * Biometrics is not allowed to verify in apps.
+     * @hide
+     */
+    int BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS = 21;
+
+    /**
      * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
      * because the authentication attempt was unsuccessful.
      * @hide
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 9bc46b9..a4f7485f 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -94,6 +94,13 @@
             BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
 
     /**
+     * Biometrics is not allowed to verify in apps.
+     * @hide
+     */
+    public static final int BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS =
+            BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+
+    /**
      * A security vulnerability has been discovered and the sensor is unavailable until a
      * security update has addressed this issue. This error can be received if for example,
      * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
index 43c0da9..48c5887 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -23,12 +23,14 @@
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.virtualhal.IVirtualHal;
 import android.os.Binder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -162,6 +164,43 @@
         dest.writeMap(mSensorPropsMap);
     }
 
+
+    /**
+     * Remap fqName of VHAL because the `virtual` instance is registered
+     * with IVirtulalHal now (IFingerprint previously)
+     * @param fqName fqName to be translated
+     * @return real fqName
+     */
+    public static String remapFqName(String fqName) {
+        if (!fqName.contains(IFingerprint.DESCRIPTOR + "/virtual")) {
+            return fqName;  //no remap needed for real hardware HAL
+        } else {
+            //new Vhal instance name
+            return fqName.replace("IFingerprint", "virtualhal.IVirtualHal");
+        }
+    }
+
+    /**
+     * @param fqName aidl interface instance name
+     * @return aidl interface
+     */
+    public static IFingerprint getIFingerprint(String fqName) {
+        if (fqName.contains("virtual")) {
+            String fqNameMapped = remapFqName(fqName);
+            Slog.i(TAG, "getIFingerprint fqName is mapped: " + fqName + "->" + fqNameMapped);
+            try {
+                IVirtualHal vhal = IVirtualHal.Stub.asInterface(
+                        Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped)));
+                return vhal.getFingerprintHal();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in vhal.getFingerprintHal() call" + fqNameMapped);
+            }
+        }
+
+        return IFingerprint.Stub.asInterface(
+                Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+    }
+
     /**
      * Returns fingerprint sensor props for the HAL {@param instance}.
      */
@@ -176,8 +215,7 @@
 
         try {
             final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
-            final IFingerprint fp = IFingerprint.Stub.asInterface(Binder.allowBlocking(
-                    ServiceManager.waitForDeclaredService(fqName)));
+            final IFingerprint fp = getIFingerprint(fqName);
             if (fp != null) {
                 props = fp.getSensorProps();
             } else {
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index d4d1ed2..dcb363c 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -55,14 +55,4 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
-}
-
-flag{
-    name: "allow_disable_ipsec_loss_detector"
-    namespace: "vcn"
-    description: "Allow disabling IPsec packet loss detector"
-    bug: "336638836"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
 }
\ No newline at end of file
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4bc3dbe..97e9f34 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -21,6 +21,8 @@
 import android.annotation.SystemApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodClassLoadHook;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -30,11 +32,9 @@
 import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.BinderInternal.CallSession;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
-import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
 
@@ -48,7 +48,6 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Modifier;
 import java.util.concurrent.atomic.AtomicReferenceArray;
-import java.util.function.Supplier;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -82,6 +81,8 @@
  *
  * @see IBinder
  */
+@RavenwoodKeepWholeClass
+@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public class Binder implements IBinder {
     /*
      * Set this flag to true to detect anonymous, local or member classes
@@ -292,33 +293,6 @@
         sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
     }
 
-    private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
-    @android.ravenwood.annotation.RavenwoodKeepWholeClass
-    private static class IdentitySupplier implements Supplier<SomeArgs> {
-        @Override
-        public SomeArgs get() {
-            final SomeArgs args = SomeArgs.obtain();
-            // Match IPCThreadState behavior
-            args.arg1 = Boolean.FALSE;
-            args.argi1 = android.os.Process.myUid();
-            args.argi2 = android.os.Process.myPid();
-            return args;
-        }
-    }
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void init$ravenwood() {
-        sIdentity$ravenwood = ThreadLocal.withInitial(new IdentitySupplier());
-    }
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void reset$ravenwood() {
-        sIdentity$ravenwood = null;
-    }
-
     /**
      * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
      */
@@ -346,14 +320,8 @@
      * 0 for a synchronous call.
      */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingPid();
 
-    /** @hide */
-    public static final int getCallingPid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
-    }
-
     /**
      * Return the Linux UID assigned to the process that sent you the
      * current transaction that is being processed. This UID can be used with
@@ -362,14 +330,8 @@
      * incoming transaction, then its own UID is returned.
      */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingUid();
 
-    /** @hide */
-    public static final int getCallingUid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
-    }
-
     /**
      * Returns {@code true} if the current thread is currently executing an
      * incoming transaction.
@@ -377,21 +339,13 @@
      * @hide
      */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native boolean isDirectlyHandlingTransactionNative();
 
-    /** @hide */
-    public static final boolean isDirectlyHandlingTransactionNative$ravenwood() {
-        // Ravenwood doesn't support IPC
-        return false;
-    }
-
     private static boolean sIsHandlingBinderTransaction = false;
 
     /**
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isDirectlyHandlingTransaction() {
         return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative();
     }
@@ -400,7 +354,6 @@
      * This is Test API which will be used to override output of isDirectlyHandlingTransactionNative
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static void setIsDirectlyHandlingTransactionOverride(boolean isInTransaction) {
         sIsHandlingBinderTransaction = isInTransaction;
     }
@@ -412,15 +365,8 @@
     * @hide
     */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     private static native boolean hasExplicitIdentity();
 
-    /** @hide */
-    private static boolean hasExplicitIdentity$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().arg1
-                == Boolean.TRUE;
-    }
-
     /**
      * Return the Linux UID assigned to the process that sent the transaction
      * currently being processed.
@@ -429,7 +375,6 @@
      * executing an incoming transaction and the calling identity has not been
      * explicitly set with {@link #clearCallingIdentity()}
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static final int getCallingUidOrThrow() {
         if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
             throw new IllegalStateException(
@@ -491,26 +436,8 @@
      * @see #restoreCallingIdentity(long)
      */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native long clearCallingIdentity();
 
-    /** @hide */
-    public static final long clearCallingIdentity$ravenwood() {
-        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
-                sIdentity$ravenwood).get();
-        long res = ((long) args.argi1 << 32) | args.argi2;
-        if (args.arg1 == Boolean.TRUE) {
-            res |= (0x1 << 30);
-        } else {
-            res &= ~(0x1 << 30);
-        }
-        // Match IPCThreadState behavior
-        args.arg1 = Boolean.TRUE;
-        args.argi1 = android.os.Process.myUid();
-        args.argi2 = android.os.Process.myPid();
-        return res;
-    }
-
     /**
      * Restore the identity of the incoming IPC on the current thread
      * back to a previously identity that was returned by {@link
@@ -522,18 +449,8 @@
      * @see #clearCallingIdentity
      */
     @CriticalNative
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void restoreCallingIdentity(long token);
 
-    /** @hide */
-    public static final void restoreCallingIdentity$ravenwood(long token) {
-        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
-                sIdentity$ravenwood).get();
-        args.arg1 = ((token & (0x1 << 30)) != 0) ? Boolean.TRUE : Boolean.FALSE;
-        args.argi1 = (int) (token >> 32);
-        args.argi2 = (int) (token & ~(0x1 << 30));
-    }
-
     /**
      * Convenience method for running the provided action enclosed in
      * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}.
@@ -708,16 +625,9 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
     public final native void markVintfStability();
 
-    /** @hide */
-    private void markVintfStability$ravenwood() {
-        // This is not useful for Ravenwood which uses local binder.
-        // TODO(b/361785059): Use real native libbinder.
-    }
-
     /**
      * Use a VINTF-stability binder w/o VINTF requirements. Should be called
      * on a binder before it is sent out of process.
@@ -736,14 +646,8 @@
      * in order to prevent the process from holding on to objects longer than
      * it needs to.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void flushPendingCommands();
 
-    /** @hide */
-    public static final void flushPendingCommands$ravenwood() {
-        // Ravenwood doesn't support IPC; ignored
-    }
-
     /**
      * Add the calling thread to the IPC thread pool. This function does
      * not return until the current process is exiting.
@@ -801,7 +705,6 @@
      * <p>If you're creating a Binder token (a Binder object without an attached interface),
      * you should use {@link #Binder(String)} instead.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public Binder() {
         this(null);
     }
@@ -818,12 +721,9 @@
      * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to
      * help identify them.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public Binder(@Nullable String descriptor) {
         mObject = getNativeBBinderHolder();
-        if (mObject != 0L) {
-            NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
-        }
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
 
         if (FIND_POTENTIAL_LEAKS) {
             final Class<? extends Binder> klass = getClass();
@@ -842,7 +742,6 @@
      * will be implemented for you to return the given owner IInterface when
      * the corresponding descriptor is requested.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
         mOwner = owner;
         mDescriptor = descriptor;
@@ -851,7 +750,6 @@
     /**
      * Default implementation returns an empty interface name.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable String getInterfaceDescriptor() {
         return mDescriptor;
     }
@@ -860,7 +758,6 @@
      * Default implementation always returns true -- if you got here,
      * the object is alive.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public boolean pingBinder() {
         return true;
     }
@@ -871,7 +768,6 @@
      * Note that if you're calling on a local binder, this always returns true
      * because your process is alive if you're calling it.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public boolean isBinderAlive() {
         return true;
     }
@@ -881,7 +777,6 @@
      * to return the associated {@link IInterface} if it matches the requested
      * descriptor.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
         if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
@@ -1080,7 +975,6 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable String getTransactionName(int transactionCode) {
         return null;
     }
@@ -1089,7 +983,6 @@
      * @hide
      */
     @VisibleForTesting
-    @android.ravenwood.annotation.RavenwoodKeep
     public final @Nullable String getTransactionTraceName(int transactionCode) {
         final boolean isInterfaceUserDefined = getMaxTransactionId() == 0;
         if (mTransactionTraceNames == null) {
@@ -1127,7 +1020,6 @@
         return transactionTraceName;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     private @NonNull String getSimpleDescriptor() {
         String descriptor = mDescriptor;
         if (descriptor == null) {
@@ -1147,7 +1039,6 @@
      * @return The highest user-defined transaction id of all transactions.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public int getMaxTransactionId() {
         return 0;
     }
@@ -1359,14 +1250,12 @@
     /**
      * Local implementation is a no-op.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
     }
 
     /**
      * Local implementation is a no-op.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
         return true;
     }
@@ -1394,13 +1283,8 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     private static native long getNativeBBinderHolder();
 
-    private static long getNativeBBinderHolder$ravenwood() {
-        return 0L;
-    }
-
     /**
      * By default, we use the calling UID since we can always trust it.
      */
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 30d2dec..a8267d1 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -28,6 +29,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.sdk.Flags;
 import android.sysprop.DeviceProperties;
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
@@ -399,12 +401,35 @@
          * device. This value never changes while a device is booted, but it may
          * increase when the hardware manufacturer provides an OTA update.
          * <p>
+         * Together with {@link SDK_MINOR_INT}, this constant defines the
+         * <pre>major.minor</pre> version of Android. <pre>SDK_INT</pre> is
+         * increased and <pre>SDK_MINOR_INT</pre> is set to 0 on new Android
+         * dessert releases. Between these, Android may also release so called
+         * minor releases where <pre>SDK_INT</pre> remains unchanged and
+         * <pre>SDK_MINOR_INT</pre> is increased. Minor releases can add new
+         * APIs, and have stricter guarantees around backwards compatibility
+         * (e.g. no changes gated by <pre>targetSdkVersion</pre>) compared to
+         * major releases.
+         * <p>
          * Possible values are defined in {@link Build.VERSION_CODES}.
          */
         public static final int SDK_INT = SystemProperties.getInt(
                 "ro.build.version.sdk", 0);
 
         /**
+         * The minor SDK version of the software currently running on this hardware
+         * device. This value never changes while a device is booted, but it may
+         * increase when the hardware manufacturer provides an OTA update.
+         * <p>
+         * Together with {@link SDK_INT}, this constant defines the
+         * <pre>major.minor</pre> version of Android. See {@link SDK_INT} for
+         * more information.
+         */
+        @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+        public static final int SDK_MINOR_INT = SystemProperties.getInt(
+                "ro.build.version.sdk_minor", 0);
+
+        /**
          * The SDK version of the software that <em>initially</em> shipped on
          * this hardware device. It <em>never</em> changes during the lifetime
          * of the device, even when {@link #SDK_INT} increases due to an OTA
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2ac2ae9..f728552 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -27,9 +27,8 @@
 import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodClassLoadHook;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodRedirect;
-import android.ravenwood.annotation.RavenwoodRedirectionClass;
 import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.text.TextUtils;
@@ -234,7 +233,7 @@
  * {@link #readSparseArray(ClassLoader, Class)}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("Parcel_host")
+@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public final class Parcel {
 
     private static final boolean DEBUG_RECYCLE = false;
@@ -387,148 +386,98 @@
     private static final int SIZE_COMPLEX_TYPE = 1;
 
     @CriticalNative
-    @RavenwoodRedirect
     private static native void nativeMarkSensitive(long nativePtr);
     @FastNative
-    @RavenwoodThrow
     private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
     @CriticalNative
-    @RavenwoodThrow
     private static native boolean nativeIsForRpc(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeDataSize(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeDataAvail(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeDataPosition(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeDataCapacity(long nativePtr);
     @FastNative
-    @RavenwoodRedirect
     private static native void nativeSetDataSize(long nativePtr, int size);
     @CriticalNative
-    @RavenwoodRedirect
     private static native void nativeSetDataPosition(long nativePtr, int pos);
     @FastNative
-    @RavenwoodRedirect
     private static native void nativeSetDataCapacity(long nativePtr, int size);
 
     @CriticalNative
-    @RavenwoodRedirect
     private static native boolean nativePushAllowFds(long nativePtr, boolean allowFds);
     @CriticalNative
-    @RavenwoodRedirect
     private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue);
 
-    @RavenwoodRedirect
     private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
-    @RavenwoodRedirect
     private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeWriteInt(long nativePtr, int val);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeWriteLong(long nativePtr, long val);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeWriteFloat(long nativePtr, float val);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeWriteDouble(long nativePtr, double val);
-    @RavenwoodThrow
     private static native void nativeSignalExceptionForError(int error);
     @FastNative
-    @RavenwoodRedirect
     private static native void nativeWriteString8(long nativePtr, String val);
     @FastNative
-    @RavenwoodRedirect
     private static native void nativeWriteString16(long nativePtr, String val);
     @FastNative
-    @RavenwoodThrow
     private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
     @FastNative
-    @RavenwoodRedirect
     private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
 
-    @RavenwoodRedirect
     private static native byte[] nativeCreateByteArray(long nativePtr);
-    @RavenwoodRedirect
     private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen);
-    @RavenwoodRedirect
     private static native byte[] nativeReadBlob(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int nativeReadInt(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native long nativeReadLong(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native float nativeReadFloat(long nativePtr);
     @CriticalNative
-    @RavenwoodRedirect
     private static native double nativeReadDouble(long nativePtr);
     @FastNative
-    @RavenwoodRedirect
     private static native String nativeReadString8(long nativePtr);
     @FastNative
-    @RavenwoodRedirect
     private static native String nativeReadString16(long nativePtr);
     @FastNative
-    @RavenwoodThrow
     private static native IBinder nativeReadStrongBinder(long nativePtr);
     @FastNative
-    @RavenwoodRedirect
     private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
 
-    @RavenwoodRedirect
     private static native long nativeCreate();
-    @RavenwoodRedirect
     private static native void nativeFreeBuffer(long nativePtr);
-    @RavenwoodRedirect
     private static native void nativeDestroy(long nativePtr);
 
-    @RavenwoodRedirect
     private static native byte[] nativeMarshall(long nativePtr);
-    @RavenwoodRedirect
     private static native void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
-    @RavenwoodRedirect
     private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
-    @RavenwoodRedirect
     private static native boolean nativeCompareDataInRange(
             long ptrA, int offsetA, long ptrB, int offsetB, int length);
-    @RavenwoodRedirect
     private static native void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int offset, int length);
     @CriticalNative
-    @RavenwoodRedirect
     private static native boolean nativeHasFileDescriptors(long nativePtr);
-    @RavenwoodRedirect
     private static native boolean nativeHasFileDescriptorsInRange(
             long nativePtr, int offset, int length);
 
-    @RavenwoodRedirect
     private static native boolean nativeHasBinders(long nativePtr);
-    @RavenwoodRedirect
     private static native boolean nativeHasBindersInRange(
             long nativePtr, int offset, int length);
-    @RavenwoodThrow
     private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
-    @RavenwoodThrow
     private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
 
     @CriticalNative
-    @RavenwoodThrow
     private static native boolean nativeReplaceCallingWorkSourceUid(
             long nativePtr, int workSourceUid);
     @CriticalNative
-    @RavenwoodThrow
     private static native int nativeReadCallingWorkSourceUid(long nativePtr);
 
     /** Last time exception with a stack trace was written */
@@ -537,7 +486,6 @@
     private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
 
     @CriticalNative
-    @RavenwoodThrow
     private static native long nativeGetOpenAshmemSize(long nativePtr);
 
     public final static Parcelable.Creator<String> STRING_CREATOR
@@ -701,12 +649,10 @@
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RavenwoodThrow
     public static native long getGlobalAllocSize();
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RavenwoodThrow
     public static native long getGlobalAllocCount();
 
     /**
@@ -1298,6 +1244,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RavenwoodThrow(blockedBy = android.text.Spanned.class)
     public final void writeCharSequence(@Nullable CharSequence val) {
         TextUtils.writeToParcel(val, this, 0);
     }
@@ -3037,7 +2984,7 @@
      * @see #writeNoException
      * @see #readException
      */
-    @RavenwoodReplace
+    @RavenwoodReplace(blockedBy = AppOpsManager.class)
     public final void writeException(@NonNull Exception e) {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
@@ -3076,10 +3023,15 @@
         }
     }
 
-    /** @hide */
-    public final void writeException$ravenwood(@NonNull Exception e) {
-        // Ravenwood doesn't support IPC, no transaction headers needed
-        writeInt(getExceptionCode(e));
+    private void writeException$ravenwood(@NonNull Exception e) {
+        int code = getExceptionCode(e);
+        writeInt(code);
+        if (code == 0) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
         writeString(e.getMessage());
         writeInt(0);
     }
@@ -3137,7 +3089,7 @@
      * @see #writeException
      * @see #readException
      */
-    @RavenwoodReplace
+    @RavenwoodReplace(blockedBy = AppOpsManager.class)
     public final void writeNoException() {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
@@ -3168,9 +3120,7 @@
         }
     }
 
-    /** @hide */
-    public final void writeNoException$ravenwood() {
-        // Ravenwood doesn't support IPC, no transaction headers needed
+    private void writeNoException$ravenwood() {
         writeInt(0);
     }
 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 50b73a9..81dc46e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2605,10 +2605,15 @@
      * (Java) thread-local policy value.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @android.ravenwood.annotation.RavenwoodReplace
     private static void onBinderStrictModePolicyChange(@ThreadPolicyMask int newPolicy) {
         setBlockGuardPolicy(newPolicy);
     }
 
+    private static void onBinderStrictModePolicyChange$ravenwood(@ThreadPolicyMask int newPolicy) {
+        /* no-op */
+    }
+
     /**
      * A tracked, critical time span. (e.g. during an animation.)
      *
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 0ed1ab6..4c9a02c 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -109,6 +109,7 @@
     private static final String TAG = "SystemClock";
 
     private static volatile IAlarmManager sIAlarmManager;
+    private static volatile ITimeDetectorService sITimeDetectorService;
 
     /**
      * Since {@code nanoTime()} is arbitrary, anchor our Ravenwood clocks against it.
@@ -188,6 +189,14 @@
         return sIAlarmManager;
     }
 
+    private static ITimeDetectorService getITimeDetectorService() {
+        if (sITimeDetectorService == null) {
+            sITimeDetectorService = ITimeDetectorService.Stub
+                    .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+        }
+        return sITimeDetectorService;
+    }
+
     /**
      * Returns milliseconds since boot, not counting time spent in deep sleep.
      *
@@ -314,15 +323,6 @@
     }
 
     /**
-     * @see #currentNetworkTimeMillis(ITimeDetectorService)
-     * @hide
-     */
-    public static long currentNetworkTimeMillis() {
-        return currentNetworkTimeMillis(ITimeDetectorService.Stub
-                .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE)));
-    }
-
-    /**
      * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
      * using a remote network source outside the device.
      * <p>
@@ -346,29 +346,29 @@
      * @throws DateTimeException when no network time can be provided.
      * @hide
      */
-    public static long currentNetworkTimeMillis(
-            ITimeDetectorService timeDetectorService) {
-        if (timeDetectorService != null) {
-            UnixEpochTime time;
-            try {
-                time = timeDetectorService.latestNetworkTime();
-            } catch (ParcelableException e) {
-                e.maybeRethrow(DateTimeException.class);
-                throw new RuntimeException(e);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-
-            if (time == null) {
-                // This is not expected.
-                throw new DateTimeException("Network based time is not available.");
-            }
-            long currentMillis = elapsedRealtime();
-            long deltaMs = currentMillis - time.getElapsedRealtimeMillis();
-            return time.getUnixEpochTimeMillis() + deltaMs;
-        } else {
+    public static long currentNetworkTimeMillis() {
+        ITimeDetectorService timeDetectorService = getITimeDetectorService();
+        if (timeDetectorService == null) {
             throw new RuntimeException(new DeadSystemException());
         }
+
+        UnixEpochTime time;
+        try {
+            time = timeDetectorService.latestNetworkTime();
+        } catch (ParcelableException e) {
+            e.maybeRethrow(DateTimeException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (time == null) {
+            // This is not expected.
+            throw new DateTimeException("Network based time is not available.");
+        }
+
+        long currentMillis = elapsedRealtime();
+        long deltaMs = currentMillis - time.getElapsedRealtimeMillis();
+        return time.getUnixEpochTimeMillis() + deltaMs;
     }
 
    /**
@@ -396,14 +396,9 @@
      */
     public static @NonNull Clock currentNetworkTimeClock() {
         return new SimpleClock(ZoneOffset.UTC) {
-            private ITimeDetectorService mSvc;
             @Override
             public long millis() {
-                if (mSvc == null) {
-                    mSvc = ITimeDetectorService.Stub
-                            .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
-                }
-                return SystemClock.currentNetworkTimeMillis(mSvc);
+                return SystemClock.currentNetworkTimeMillis();
             }
         };
     }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 5174005..6c486db 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -241,3 +241,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "wallet_role_icon_property_enabled"
+    is_exported: true
+    namespace: "wallet_integration"
+    description: "This flag is used to enabled the Wallet Role s icon fetching from manifest property"
+    bug: "349942654"
+}
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index bbe9cdb..c2ad508 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -28,27 +28,6 @@
  */
 public class ClientFlags {
     /**
-     * @see Flags#noBreakNoHyphenationSpan()
-     */
-    public static boolean noBreakNoHyphenationSpan() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN);
-    }
-
-    /**
-     * @see Flags#fixLineHeightForLocale()
-     */
-    public static boolean fixLineHeightForLocale() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
-    }
-
-    /**
-     * @see Flags#icuBidiMigration()
-     */
-    public static boolean icuBidiMigration() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_ICU_BIDI_MIGRATION);
-    }
-
-    /**
      * @see Flags#fixMisalignedContextMenu()
      */
     public static boolean fixMisalignedContextMenu() {
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 896e087..31a2263 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -42,8 +42,6 @@
 import android.text.style.ReplacementSpan;
 import android.util.Pools.SynchronizedPool;
 
-import com.android.text.flags.Flags;
-
 import java.util.Arrays;
 
 /**
@@ -201,14 +199,11 @@
      * @hide
      */
     public @Layout.Direction int getParagraphDir() {
-        if (Flags.icuBidiMigration()) {
-            if (mBidi == null) {
-                return Layout.DIR_LEFT_TO_RIGHT;
-            }
-            return (mBidi.getParaLevel() & 0x01) == 0
-                    ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+        if (mBidi == null) {
+            return Layout.DIR_LEFT_TO_RIGHT;
         }
-        return mParaDir;
+        return (mBidi.getParaLevel() & 0x01) == 0
+                ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
     }
 
     /**
@@ -219,71 +214,62 @@
      */
     public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
                                     @IntRange(from = 0) int end) {  // exclusive
-        if (Flags.icuBidiMigration()) {
-            // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
-            if (mBidi == null) {
-                return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-            }
-
-            // Easy case: If the original text only contains single directionality run, the
-            // substring is only single run.
-            if (start == end) {
-                if ((mBidi.getParaLevel() & 0x01) == 0) {
-                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-                } else {
-                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
-                }
-            }
-
-            // Okay, now we need to generate the line instance.
-            Bidi bidi = mBidi.createLineBidi(start, end);
-
-            // Easy case: If the line instance only contains single directionality run, no need
-            // to reorder visually.
-            if (bidi.getRunCount() == 1) {
-                if (bidi.getRunLevel(0) == 1) {
-                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
-                } else if (bidi.getRunLevel(0) == 0) {
-                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-                } else {
-                    return new Directions(new int[] {
-                            0, bidi.getRunLevel(0) << Layout.RUN_LEVEL_SHIFT | (end - start)});
-                }
-            }
-
-            // Reorder directionality run visually.
-            byte[] levels = new byte[bidi.getRunCount()];
-            for (int i = 0; i < bidi.getRunCount(); ++i) {
-                levels[i] = (byte) bidi.getRunLevel(i);
-            }
-            int[] visualOrders = Bidi.reorderVisual(levels);
-
-            int[] dirs = new int[bidi.getRunCount() * 2];
-            for (int i = 0; i < bidi.getRunCount(); ++i) {
-                int vIndex;
-                if ((mBidi.getBaseLevel() & 0x01) == 1) {
-                    // For the historical reasons, if the base directionality is RTL, the Android
-                    // draws from the right, i.e. the visually reordered run needs to be reversed.
-                    vIndex = visualOrders[bidi.getRunCount() - i - 1];
-                } else {
-                    vIndex = visualOrders[i];
-                }
-
-                // Special packing of dire
-                dirs[i * 2] = bidi.getRunStart(vIndex);
-                dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
-                        | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
-            }
-
-            return new Directions(dirs);
-        }
-        if (mLtrWithoutBidi) {
+        // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
+        if (mBidi == null) {
             return Layout.DIRS_ALL_LEFT_TO_RIGHT;
         }
 
-        final int length = end - start;
-        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
-                length);
+        // Easy case: If the original text only contains single directionality run, the
+        // substring is only single run.
+        if (start == end) {
+            if ((mBidi.getParaLevel() & 0x01) == 0) {
+                return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+            } else {
+                return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+            }
+        }
+
+        // Okay, now we need to generate the line instance.
+        Bidi bidi = mBidi.createLineBidi(start, end);
+
+        // Easy case: If the line instance only contains single directionality run, no need
+        // to reorder visually.
+        if (bidi.getRunCount() == 1) {
+            if (bidi.getRunLevel(0) == 1) {
+                return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+            } else if (bidi.getRunLevel(0) == 0) {
+                return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+            } else {
+                return new Directions(new int[] {
+                        0, bidi.getRunLevel(0) << Layout.RUN_LEVEL_SHIFT | (end - start)});
+            }
+        }
+
+        // Reorder directionality run visually.
+        byte[] levels = new byte[bidi.getRunCount()];
+        for (int i = 0; i < bidi.getRunCount(); ++i) {
+            levels[i] = (byte) bidi.getRunLevel(i);
+        }
+        int[] visualOrders = Bidi.reorderVisual(levels);
+
+        int[] dirs = new int[bidi.getRunCount() * 2];
+        for (int i = 0; i < bidi.getRunCount(); ++i) {
+            int vIndex;
+            if ((mBidi.getBaseLevel() & 0x01) == 1) {
+                // For the historical reasons, if the base directionality is RTL, the Android
+                // draws from the right, i.e. the visually reordered run needs to be reversed.
+                vIndex = visualOrders[bidi.getRunCount() - i - 1];
+            } else {
+                vIndex = visualOrders[i];
+            }
+
+            // Special packing of dire
+            dirs[i * 2] = bidi.getRunStart(vIndex);
+            dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
+                    | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
+        }
+
+        return new Directions(dirs);
     }
 
     /**
@@ -681,84 +667,56 @@
             }
         }
 
-        if (Flags.icuBidiMigration()) {
-            if ((textDir == TextDirectionHeuristics.LTR
-                    || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
-                    || textDir == TextDirectionHeuristics.ANYRTL_LTR)
-                    && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
-                mLevels.clear();
-                mLtrWithoutBidi = true;
-                return;
-            }
-            final int bidiRequest;
-            if (textDir == TextDirectionHeuristics.LTR) {
-                bidiRequest = Bidi.LTR;
-            } else if (textDir == TextDirectionHeuristics.RTL) {
-                bidiRequest = Bidi.RTL;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
-                bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
-                bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
-            } else {
-                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
-                bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
-            }
-            mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
-
-            if (mCopiedBuffer.length > 0
-                    && mBidi.getParagraphIndex(mCopiedBuffer.length - 1) != 0) {
-                // Historically, the MeasuredParagraph does not treat the CR letters as paragraph
-                // breaker but ICU BiDi treats it as paragraph breaker. In the MeasureParagraph,
-                // the given range always represents a single paragraph, so if the BiDi object has
-                // multiple paragraph, it should contains a CR letters in the text. Using CR is not
-                // common in Android and also it should not penalize the easy case, e.g. all LTR,
-                // check the paragraph count here and replace the CR letters and re-calculate
-                // BiDi again.
-                for (int i = 0; i < mTextLength; ++i) {
-                    if (Character.isSurrogate(mCopiedBuffer[i])) {
-                        // All block separators are in BMP.
-                        continue;
-                    }
-                    if (UCharacter.getDirection(mCopiedBuffer[i])
-                            == UCharacterDirection.BLOCK_SEPARATOR) {
-                        mCopiedBuffer[i] = OBJECT_REPLACEMENT_CHARACTER;
-                    }
-                }
-                mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
-            }
-            mLevels.resize(mTextLength);
-            byte[] rawArray = mLevels.getRawArray();
-            for (int i = 0; i < mTextLength; ++i) {
-                rawArray[i] = mBidi.getLevelAt(i);
-            }
-            mLtrWithoutBidi = false;
-            return;
-        }
         if ((textDir == TextDirectionHeuristics.LTR
                 || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
                 || textDir == TextDirectionHeuristics.ANYRTL_LTR)
                 && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
             mLevels.clear();
-            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
             mLtrWithoutBidi = true;
-        } else {
-            final int bidiRequest;
-            if (textDir == TextDirectionHeuristics.LTR) {
-                bidiRequest = Layout.DIR_REQUEST_LTR;
-            } else if (textDir == TextDirectionHeuristics.RTL) {
-                bidiRequest = Layout.DIR_REQUEST_RTL;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
-            } else {
-                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
-                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
-            }
-            mLevels.resize(mTextLength);
-            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
-            mLtrWithoutBidi = false;
+            return;
         }
+        final int bidiRequest;
+        if (textDir == TextDirectionHeuristics.LTR) {
+            bidiRequest = Bidi.LTR;
+        } else if (textDir == TextDirectionHeuristics.RTL) {
+            bidiRequest = Bidi.RTL;
+        } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+            bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
+        } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+            bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
+        } else {
+            final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+            bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
+        }
+        mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+
+        if (mCopiedBuffer.length > 0
+                && mBidi.getParagraphIndex(mCopiedBuffer.length - 1) != 0) {
+            // Historically, the MeasuredParagraph does not treat the CR letters as paragraph
+            // breaker but ICU BiDi treats it as paragraph breaker. In the MeasureParagraph,
+            // the given range always represents a single paragraph, so if the BiDi object has
+            // multiple paragraph, it should contains a CR letters in the text. Using CR is not
+            // common in Android and also it should not penalize the easy case, e.g. all LTR,
+            // check the paragraph count here and replace the CR letters and re-calculate
+            // BiDi again.
+            for (int i = 0; i < mTextLength; ++i) {
+                if (Character.isSurrogate(mCopiedBuffer[i])) {
+                    // All block separators are in BMP.
+                    continue;
+                }
+                if (UCharacter.getDirection(mCopiedBuffer[i])
+                        == UCharacterDirection.BLOCK_SEPARATOR) {
+                    mCopiedBuffer[i] = OBJECT_REPLACEMENT_CHARACTER;
+                }
+            }
+            mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+        }
+        mLevels.resize(mTextLength);
+        byte[] rawArray = mLevels.getRawArray();
+        for (int i = 0; i < mTextLength; ++i) {
+            rawArray[i] = mBidi.getLevelAt(i);
+        }
+        mLtrWithoutBidi = false;
     }
 
     private void applyReplacementRun(@NonNull ReplacementSpan replacement,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 0f1b031..076721f 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,9 +55,6 @@
      * List of text flags to be transferred to the application process.
      */
     public static final String[] TEXT_ACONFIGS_FLAGS = {
-            Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
-            Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
-            Flags.FLAG_ICU_BIDI_MIGRATION,
             Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
     };
 
@@ -67,9 +64,6 @@
      * The order must be the same to the TEXT_ACONFIG_FLAGS.
      */
     public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
-            Flags.noBreakNoHyphenationSpan(),
-            Flags.fixLineHeightForLocale(),
-            Flags.icuBidiMigration(),
             Flags.fixMisalignedContextMenu(),
     };
 
diff --git a/core/java/android/text/flags/24Q3.aconfig b/core/java/android/text/flags/24Q3.aconfig
new file mode 100644
index 0000000..7035fc8
--- /dev/null
+++ b/core/java/android/text/flags/24Q3.aconfig
@@ -0,0 +1,54 @@
+package: "com.android.text.flags"
+container: "system"
+
+# This aconfig file contains released flags in 24Q3 those cannot be removed.
+
+flag {
+  name: "use_bounds_for_width"
+  is_exported: true
+  namespace: "text"
+  description: "Feature flag for preventing horizontal clipping."
+  bug: "63938206"
+}
+
+flag {
+  name: "word_style_auto"
+  is_exported: true
+  namespace: "text"
+  description: "A feature flag that implements line break word style auto."
+  bug: "280005585"
+}
+
+flag {
+  name: "letter_spacing_justification"
+  is_exported: true
+  namespace: "text"
+  description: "A feature flag that implement inter character justification."
+  bug: "283193133"
+}
+
+flag {
+  name: "fix_line_height_for_locale"
+  is_exported: true
+  namespace: "text"
+  description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+  bug: "303326708"
+}
+
+flag {
+  name: "new_fonts_fallback_xml"
+  is_exported: true
+  namespace: "text"
+  description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+  # Make read only, as it could be used before the Settings provider is initialized.
+  is_fixed_read_only: true
+  bug: "281769620"
+}
+
+flag {
+  name: "no_break_no_hyphenation_span"
+  is_exported: true
+  namespace: "text"
+  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+  bug: "283193586"
+}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index b2be1a7..3c61f4f 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -2,32 +2,6 @@
 container: "system"
 
 flag {
-  name: "new_fonts_fallback_xml"
-  is_exported: true
-  namespace: "text"
-  description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
-  # Make read only, as it could be used before the Settings provider is initialized.
-  is_fixed_read_only: true
-  bug: "281769620"
-}
-
-flag {
-  name: "fix_line_height_for_locale"
-  is_exported: true
-  namespace: "text"
-  description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
-  bug: "303326708"
-}
-
-flag {
-  name: "no_break_no_hyphenation_span"
-  is_exported: true
-  namespace: "text"
-  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
-  bug: "283193586"
-}
-
-flag {
   name: "use_optimized_boottime_font_loading"
   namespace: "text"
   description: "Feature flag ensuring that font is loaded once and asynchronously."
@@ -51,37 +25,6 @@
 }
 
 flag {
-  name: "use_bounds_for_width"
-  is_exported: true
-  namespace: "text"
-  description: "Feature flag for preventing horizontal clipping."
-  bug: "63938206"
-}
-
-flag {
-  name: "deprecate_ui_fonts"
-  namespace: "text"
-  description: "Feature flag for deprecating UI fonts. By setting true for this feature flag, the elegant text height of will be turned on by default unless explicitly setting it to false by attribute or Java API call."
-  bug: "279646685"
-}
-
-flag {
-  name: "word_style_auto"
-  is_exported: true
-  namespace: "text"
-  description: "A feature flag that implements line break word style auto."
-  bug: "280005585"
-}
-
-flag {
-  name: "letter_spacing_justification"
-  is_exported: true
-  namespace: "text"
-  description: "A feature flag that implement inter character justification."
-  bug: "283193133"
-}
-
-flag {
   name: "escape_clears_focus"
   namespace: "text"
   description: "Feature flag for clearing focus when the escape key is pressed."
@@ -120,22 +63,6 @@
 }
 
 flag {
-  name: "icu_bidi_migration"
-  namespace: "text"
-  description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
-  bug: "317144801"
-}
-
-flag {
-  name: "lazy_variation_instance"
-  namespace: "text"
-  description: "A flag for enabling lazy variation instance creation."
-  # Make read only, as it could be used before the Settings provider is initialized.
-  is_fixed_read_only: true
-  bug: "324676775"
-}
-
-flag {
   name: "handwriting_end_of_line_tap"
   namespace: "text"
   description: "Initiate handwriting when stylus taps at the end of a line in a focused non-empty TextView with the cursor at the end of that line"
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 9ecb4cb..b2017a5 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -224,7 +224,6 @@
     /**
      * Similar to {@link #wtf(String, String)}, but does not output anything to the log.
      */
-    @android.ravenwood.annotation.RavenwoodThrow
     public static void wtfQuiet(@Nullable String tag, @NonNull String msg) {
         Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
     }
@@ -243,7 +242,6 @@
      * @see Log#wtfStack(String, String)
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    @android.ravenwood.annotation.RavenwoodThrow
     public static int wtfStack(@Nullable String tag, @NonNull String msg) {
         return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7b4ea41..0ed0e60 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -371,7 +371,7 @@
  *     </tr>
  *     <tr>
  *         <td><code>{@link #onTouchEvent(MotionEvent)}</code></td>
- *         <td>Called when a touch screen motion event occurs.
+ *         <td>Called when a motion event occurs with pointers down on the view.
  *         </td>
  *     </tr>
  *     <tr>
@@ -17873,7 +17873,13 @@
     }
 
     /**
-     * Implement this method to handle touch screen motion events.
+     * Implement this method to handle pointer events.
+     * <p>
+     * This method is called to handle motion events where pointers are down on
+     * the view. For example, this could include touchscreen touches, stylus
+     * touches, or click-and-drag events from a mouse. However, it is not called
+     * for motion events that do not involve pointers being down, such as hover
+     * events or mouse scroll wheel movements.
      * <p>
      * If this method is used to detect click actions, it is recommended that
      * the actions be performed by implementing and calling
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 3b5286a..2237417 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4504,6 +4504,11 @@
         final View[] children = mChildren;
         for (int i = 0; i < count; i++) {
             final View child = children[i];
+            if (child == null) {
+                throw new IllegalStateException(getClass().getSimpleName() + " contains null " +
+                        "child at index " + i + " when traversal in dispatchGetDisplayList," +
+                        " the view may have been removed.");
+            }
             if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                 recreateChildDisplayList(child);
             }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 67a207e..4fe11e6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -107,6 +107,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -2371,7 +2372,7 @@
         public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
 
         /**
-         * Window type: the drag-and-drop pseudowindow.  There is only one
+         * Window type: the drag-and-drop pseudowindow. There is only one
          * drag layer (at most), and it is placed on top of all other windows.
          * In multiuser systems shows only on the owning user's window.
          * @hide
@@ -2381,7 +2382,7 @@
         /**
          * Window type: panel that slides out from over the status bar
          * In multiuser systems shows on all users' windows. These windows
-         * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL}
+         * are displayed on top of the status bar and any {@link #TYPE_STATUS_BAR_PANEL}
          * windows.
          * @hide
          */
@@ -4649,6 +4650,30 @@
         public InsetsFrameProvider[] providedInsets;
 
         /**
+         * Sets the insets to be provided by the window.
+         *
+         * @param insetsParams The parameters for the insets to be provided by the window.
+         *
+         * @hide
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_STATUS_BAR_AND_INSETS)
+        @SystemApi
+        public void setInsetsParams(@NonNull List<InsetsParams> insetsParams) {
+            if (insetsParams.isEmpty()) {
+                providedInsets = null;
+            } else {
+                providedInsets = new InsetsFrameProvider[insetsParams.size()];
+                for (int i = 0; i < insetsParams.size(); ++i) {
+                    final InsetsParams params = insetsParams.get(i);
+                    providedInsets[i] =
+                            new InsetsFrameProvider(/* owner= */ this, /* index= */ i,
+                                    params.getType())
+                                    .setInsetsSize(params.getInsetsSize());
+                }
+            }
+        }
+
+        /**
          * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this
          * method won't affect the app's layout. This field only takes effects if the caller has
          * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as
@@ -6117,6 +6142,55 @@
     }
 
     /**
+     * Specifies the parameters of the insets provided by a window.
+     *
+     * @see WindowManager.LayoutParams#setInsetsParams(List)
+     * @see android.graphics.Insets
+     *
+     * @hide
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_STATUS_BAR_AND_INSETS)
+    @SystemApi
+    public static class InsetsParams {
+
+        private final @InsetsType int mType;
+        private @Nullable Insets mInsets;
+
+        /**
+         * Creates an instance of InsetsParams.
+         *
+         * @param type the type of insets to provide, e.g. {@link WindowInsets.Type#statusBars()}.
+         * @see WindowInsets.Type
+         */
+        public InsetsParams(@InsetsType int type) {
+            mType = type;
+        }
+
+        /**
+         * Sets the size of the provided insets. If {@code null}, then the provided insets will
+         * have the same size as the window frame.
+         */
+        public @NonNull InsetsParams setInsetsSize(@Nullable Insets insets) {
+            mInsets = insets;
+            return this;
+        }
+
+        /**
+         * Returns the type of provided insets.
+         */
+        public @InsetsType int getType() {
+            return mType;
+        }
+
+        /**
+         * Returns the size of the provided insets.
+         */
+        public @Nullable Insets getInsetsSize() {
+            return mInsets;
+        }
+    }
+
+    /**
      * Holds the WM lock for the specified amount of milliseconds.
      * Intended for use by the tests that need to imitate lock contention.
      * The token should be obtained by
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index a87e5c8..2b7cf42 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1774,7 +1774,8 @@
     }
 
     /**
-     * Notifies that the accessibility button in the system's navigation area has been clicked
+     * Notifies that the accessibility button in the system's navigation area has been clicked,
+     * or a gesture shortcut input has been performed.
      *
      * @param displayId The logical display id.
      * @hide
@@ -1785,7 +1786,8 @@
     }
 
     /**
-     * Perform the accessibility button for the given target which is assigned to the button.
+     * Perform the accessibility button or gesture
+     * for the given target which is assigned to the button.
      *
      * @param displayId displayId The logical display id.
      * @param targetName The flattened {@link ComponentName} string or the class name of a system
@@ -1810,6 +1812,31 @@
     }
 
     /**
+     * Notifies that a shortcut was long-clicked.
+     * This displays the dialog used to select which target the given shortcut will use,
+     * from its list of targets.
+     * The current shortcut type is determined by the current navigation mode.
+     *
+     * @param displayId The id of the display to show the dialog on.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
+    public void notifyAccessibilityButtonLongClicked(int displayId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonLongClicked(displayId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button long click. ", re);
+        }
+    }
+
+    /**
      * Notifies that the visibility of the accessibility button in the system's navigation area
      * has changed.
      *
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 2de3ce8..e04fa15 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -88,6 +88,8 @@
     @EnforcePermission("STATUS_BAR_SERVICE")
     void notifyAccessibilityButtonClicked(int displayId, String targetName);
 
+    @EnforcePermission("STATUS_BAR_SERVICE")
+    void notifyAccessibilityButtonLongClicked(int displayId);
 
     @EnforcePermission("STATUS_BAR_SERVICE")
     void notifyAccessibilityButtonVisibilityChanged(boolean available);
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 1da2af4..b18dbbc 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -15,12 +15,14 @@
  */
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
+import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import java.util.ArrayList;
@@ -31,30 +33,31 @@
  * @hide
  */
 public class UserPackage {
-    private final UserInfo mUserInfo;
+    private final UserHandle mUser;
     private final PackageInfo mPackageInfo;
 
     public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU;
 
-    public UserPackage(UserInfo user, PackageInfo packageInfo) {
-        this.mUserInfo = user;
-        this.mPackageInfo = packageInfo;
+    public UserPackage(@NonNull UserHandle user, @Nullable PackageInfo packageInfo) {
+        mUser = user;
+        mPackageInfo = packageInfo;
     }
 
     /**
      * Returns a list of (User,PackageInfo) pairs corresponding to the PackageInfos for all
      * device users for the package named {@param packageName}.
      */
-    public static List<UserPackage> getPackageInfosAllUsers(Context context,
-            String packageName, int packageFlags) {
-        List<UserInfo> users = getAllUsers(context);
+    public static @NonNull List<UserPackage> getPackageInfosAllUsers(@NonNull Context context,
+            @NonNull String packageName, int packageFlags) {
+        UserManager userManager = context.getSystemService(UserManager.class);
+        List<UserHandle> users = userManager.getUserHandles(false);
         List<UserPackage> userPackages = new ArrayList<UserPackage>(users.size());
-        for (UserInfo user : users) {
+        for (UserHandle user : users) {
+            PackageManager pm = context.createContextAsUser(user, 0).getPackageManager();
             PackageInfo packageInfo = null;
             try {
-                packageInfo = context.getPackageManager().getPackageInfoAsUser(
-                        packageName, packageFlags, user.id);
-            } catch (NameNotFoundException e) {
+                packageInfo = pm.getPackageInfo(packageName, packageFlags);
+            } catch (PackageManager.NameNotFoundException e) {
             }
             userPackages.add(new UserPackage(user, packageInfo));
         }
@@ -88,18 +91,11 @@
         return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
     }
 
-    public UserInfo getUserInfo() {
-        return mUserInfo;
+    public UserHandle getUser() {
+        return mUser;
     }
 
     public PackageInfo getPackageInfo() {
         return mPackageInfo;
     }
-
-
-    private static List<UserInfo> getAllUsers(Context context) {
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        return userManager.getUsers();
-    }
-
 }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index b5bf529..511c832 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.enableTouchScrollFeedback;
 import static android.view.flags.Flags.viewVelocityApi;
 
 import android.annotation.ColorInt;
@@ -846,6 +847,8 @@
                         deltaY += mTouchSlop;
                     }
                 }
+                boolean hitTopLimit = false;
+                boolean hitBottomLimit = false;
                 if (mIsBeingDragged) {
                     // Scroll to follow the motion event
                     mLastMotionY = y - mScrollOffset[1];
@@ -889,12 +892,14 @@
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
+                            hitTopLimit = true;
                         } else if (pulledToY > range) {
                             mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
                                     1.f - displacement);
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
+                            hitBottomLimit = true;
                         }
                         if (shouldDisplayEdgeEffects()
                                 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
@@ -902,6 +907,20 @@
                         }
                     }
                 }
+
+                // TODO: b/360198915 - Add unit tests.
+                if (enableTouchScrollFeedback()) {
+                    if (hitTopLimit || hitBottomLimit) {
+                        initHapticScrollFeedbackProviderIfNotExists();
+                        mHapticScrollFeedbackProvider.onScrollLimit(vtev.getDeviceId(),
+                                vtev.getSource(), MotionEvent.AXIS_Y,
+                                /* isStart= */ hitTopLimit);
+                    } else if (Math.abs(deltaY) != 0) {
+                        initHapticScrollFeedbackProviderIfNotExists();
+                        mHapticScrollFeedbackProvider.onScrollProgress(vtev.getDeviceId(),
+                                vtev.getSource(), MotionEvent.AXIS_Y, deltaY);
+                    }
+                }
                 break;
             case MotionEvent.ACTION_UP:
                 if (mIsBeingDragged) {
diff --git a/core/java/android/widget/flags/flags.aconfig b/core/java/android/widget/flags/flags.aconfig
new file mode 100644
index 0000000..f0ed83b
--- /dev/null
+++ b/core/java/android/widget/flags/flags.aconfig
@@ -0,0 +1,11 @@
+package: "android.widget.flags"
+container: "system"
+flag {
+  name: "enable_fading_view_group"
+  namespace: "system_performance"
+  description: "FRP screen during OOBE must have fading and scaling animation in Wear Watches"
+  bug: "348515581"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3998ac6..8e81951 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -251,3 +251,10 @@
     description: "Persists the desktop windowing session across reboots."
     bug: "350456942"
 }
+
+flag {
+    name: "enable_display_focus_in_shell_transitions"
+    namespace: "lse_desktop_experience"
+    description: "Creates a shell transition when display focus switches."
+    bug: "356109871"
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index 01cbb55..81d8adf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -20,7 +20,6 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityButtonLongPressStatus;
@@ -44,6 +43,8 @@
  * Activity used to display and persist a service or feature target for the Accessibility button.
  */
 public class AccessibilityButtonChooserActivity extends Activity {
+    public static final String EXTRA_TYPE_TO_CHOOSE = "TYPE";
+
     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
 
     @Override
@@ -67,8 +68,8 @@
                 NAV_BAR_MODE_GESTURAL == getResources().getInteger(
                         com.android.internal.R.integer.config_navBarInteractionMode);
 
-        final int targetType = (isGestureNavigateEnabled
-                && android.provider.Flags.a11yStandaloneGestureEnabled()) ? GESTURE : SOFTWARE;
+        final int targetType = android.provider.Flags.a11yStandaloneGestureEnabled()
+                ? getIntent().getIntExtra(EXTRA_TYPE_TO_CHOOSE, SOFTWARE) : SOFTWARE;
 
         if (isGestureNavigateEnabled) {
             final TextView promptPrologue = findViewById(R.id.accessibility_button_prompt_prologue);
diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig
index b6b8bc5..82f50ae 100644
--- a/core/java/com/android/internal/jank/flags.aconfig
+++ b/core/java/com/android/internal/jank/flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
   name: "use_sf_frame_duration"
-  namespace: "android_platform_window_surfaces"
+  namespace: "window_surfaces"
   description: "Whether to get the frame duration from SurfaceFlinger, or HWUI"
   bug: "354763298"
   is_fixed_read_only: true
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 032ac42..12d3264 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -3025,7 +3025,6 @@
     @Override
     public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
         this.splitCodePaths = splitCodePaths;
-        this.mSplits = null; // reset for paths changed
         if (splitCodePaths != null) {
             int size = splitCodePaths.length;
             for (int index = 0; index < size; index++) {
diff --git a/core/java/com/android/internal/widget/ViewGroupFader.java b/core/java/com/android/internal/widget/ViewGroupFader.java
index b54023a..21206c2 100644
--- a/core/java/com/android/internal/widget/ViewGroupFader.java
+++ b/core/java/com/android/internal/widget/ViewGroupFader.java
@@ -16,12 +16,14 @@
 
 package com.android.internal.widget;
 
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.animation.BaseInterpolator;
 import android.view.animation.PathInterpolator;
+import android.widget.flags.Flags;
 
 /**
  * This class is ported from
@@ -36,7 +38,7 @@
  * height of the child. When not in the top or bottom regions, children have their default alpha and
  * scale.
  */
-class ViewGroupFader {
+public class ViewGroupFader {
 
     private static final float SCALE_LOWER_BOUND = 0.7f;
     private float mScaleLowerBound = SCALE_LOWER_BOUND;
@@ -68,7 +70,7 @@
     private BaseInterpolator mBottomInterpolator = new PathInterpolator(0.3f, 0f, 0.7f, 1f);
 
     /** Callback which is called when attempting to fade a view. */
-    interface AnimationCallback {
+    public interface AnimationCallback {
         boolean shouldFadeFromTop(View view);
 
         boolean shouldFadeFromBottom(View view);
@@ -82,7 +84,7 @@
      * of the current position.
      */
     // TODO(b/182846214): Clean up the interface design to avoid exposing too much details to users.
-    interface ChildViewBoundsProvider {
+    public interface ChildViewBoundsProvider {
         /**
          * Provide the bounds of the child view.
          *
@@ -168,7 +170,7 @@
         }
     }
 
-    ViewGroupFader(
+    public ViewGroupFader(
             ViewGroup parent,
             AnimationCallback callback,
             ChildViewBoundsProvider childViewBoundsProvider) {
@@ -212,7 +214,7 @@
         this.mContainerBoundsProvider = boundsProvider;
     }
 
-    void updateFade() {
+    public void updateFade() {
         mContainerBoundsProvider.provideBounds(mParent, mContainerBounds);
         mTopBoundPixels = mContainerBounds.height() * mChainedBoundsTop;
         mBottomBoundPixels = mContainerBounds.height() * mChainedBoundsBottom;
@@ -221,13 +223,20 @@
     }
 
     /** For each list element, calculate and adjust the scale and alpha based on its position */
-    private void updateListElementFades(ViewGroup parent, boolean shouldFade) {
+    public void updateListElementFades(ViewGroup parent, boolean shouldFade) {
         for (int i = 0; i < parent.getChildCount(); i++) {
             View child = parent.getChildAt(i);
             if (child.getVisibility() != View.VISIBLE) {
                 continue;
             }
 
+            if (Flags.enableFadingViewGroup() && Resources.getSystem().getBoolean(
+                    com.android.internal.R.bool.config_enableViewGroupScalingFading)) {
+                if (child instanceof ViewGroup) {
+                    updateListElementFades((ViewGroup) child, true);
+                }
+            }
+
             if (shouldFade) {
                 fadeElement(parent, child);
             }
@@ -312,4 +321,4 @@
     private static float lerp(float min, float max, float fraction) {
         return min + (max - min) * fraction;
     }
-}
+}
\ No newline at end of file
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 584ebaa..dec724b 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -90,7 +90,7 @@
     env->CallVoidMethod(parcelObj, gParcelOffsets.recycle);
 }
 
-static void android_os_Parcel_markSensitive(jlong nativePtr)
+static void android_os_Parcel_markSensitive(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel) {
@@ -116,30 +116,30 @@
     }
 }
 
-static jboolean android_os_Parcel_isForRpc(jlong nativePtr) {
+static jboolean android_os_Parcel_isForRpc(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return parcel ? parcel->isForRpc() : false;
 }
 
-static jint android_os_Parcel_dataSize(jlong nativePtr)
+static jint android_os_Parcel_dataSize(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return parcel ? parcel->dataSize() : 0;
 }
 
-static jint android_os_Parcel_dataAvail(jlong nativePtr)
+static jint android_os_Parcel_dataAvail(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return parcel ? parcel->dataAvail() : 0;
 }
 
-static jint android_os_Parcel_dataPosition(jlong nativePtr)
+static jint android_os_Parcel_dataPosition(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return parcel ? parcel->dataPosition() : 0;
 }
 
-static jint android_os_Parcel_dataCapacity(jlong nativePtr)
+static jint android_os_Parcel_dataCapacity(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return parcel ? parcel->dataCapacity() : 0;
@@ -156,7 +156,7 @@
     }
 }
 
-static void android_os_Parcel_setDataPosition(jlong nativePtr, jint pos)
+static void android_os_Parcel_setDataPosition(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pos)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -175,7 +175,8 @@
     }
 }
 
-static jboolean android_os_Parcel_pushAllowFds(jlong nativePtr, jboolean allowFds)
+static jboolean android_os_Parcel_pushAllowFds(CRITICAL_JNI_PARAMS_COMMA
+                                               jlong nativePtr, jboolean allowFds)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     jboolean ret = JNI_TRUE;
@@ -185,7 +186,8 @@
     return ret;
 }
 
-static void android_os_Parcel_restoreAllowFds(jlong nativePtr, jboolean lastValue)
+static void android_os_Parcel_restoreAllowFds(CRITICAL_JNI_PARAMS_COMMA
+                                              jlong nativePtr, jboolean lastValue)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -259,22 +261,22 @@
     blob.release();
 }
 
-static int android_os_Parcel_writeInt(jlong nativePtr, jint val) {
+static int android_os_Parcel_writeInt(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint val) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return (parcel != NULL) ? parcel->writeInt32(val) : OK;
 }
 
-static int android_os_Parcel_writeLong(jlong nativePtr, jlong val) {
+static int android_os_Parcel_writeLong(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong val) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return (parcel != NULL) ? parcel->writeInt64(val) : OK;
 }
 
-static int android_os_Parcel_writeFloat(jlong nativePtr, jfloat val) {
+static int android_os_Parcel_writeFloat(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat val) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return (parcel != NULL) ? parcel->writeFloat(val) : OK;
 }
 
-static int android_os_Parcel_writeDouble(jlong nativePtr, jdouble val) {
+static int android_os_Parcel_writeDouble(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jdouble val) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     return (parcel != NULL) ? parcel->writeDouble(val) : OK;
 }
@@ -446,7 +448,7 @@
     return ret;
 }
 
-static jint android_os_Parcel_readInt(jlong nativePtr)
+static jint android_os_Parcel_readInt(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -455,7 +457,7 @@
     return 0;
 }
 
-static jlong android_os_Parcel_readLong(jlong nativePtr)
+static jlong android_os_Parcel_readLong(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -464,7 +466,7 @@
     return 0;
 }
 
-static jfloat android_os_Parcel_readFloat(jlong nativePtr)
+static jfloat android_os_Parcel_readFloat(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -473,7 +475,7 @@
     return 0;
 }
 
-static jdouble android_os_Parcel_readDouble(jlong nativePtr)
+static jdouble android_os_Parcel_readDouble(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -690,7 +692,7 @@
     return JNI_FALSE;
 }
 
-static jboolean android_os_Parcel_hasFileDescriptors(jlong nativePtr)
+static jboolean android_os_Parcel_hasFileDescriptors(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     jboolean ret = JNI_FALSE;
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -807,7 +809,7 @@
     return Parcel::getGlobalAllocCount();
 }
 
-static jlong android_os_Parcel_getOpenAshmemSize(jlong nativePtr)
+static jlong android_os_Parcel_getOpenAshmemSize(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -816,7 +818,7 @@
     return 0;
 }
 
-static jint android_os_Parcel_readCallingWorkSourceUid(jlong nativePtr)
+static jint android_os_Parcel_readCallingWorkSourceUid(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -825,7 +827,8 @@
     return IPCThreadState::kUnsetWorkSource;
 }
 
-static jboolean android_os_Parcel_replaceCallingWorkSourceUid(jlong nativePtr, jint uid)
+static jboolean android_os_Parcel_replaceCallingWorkSourceUid(CRITICAL_JNI_PARAMS_COMMA
+                                                              jlong nativePtr, jint uid)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 921b77d..f2c70b5 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1152,60 +1152,60 @@
 
 // ----------------------------------------------------------------------------
 
-static jint android_os_Binder_getCallingPid()
+static jint android_os_Binder_getCallingPid(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->getCallingPid();
 }
 
-static jint android_os_Binder_getCallingUid()
+static jint android_os_Binder_getCallingUid(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->getCallingUid();
 }
 
-static jboolean android_os_Binder_isDirectlyHandlingTransactionNative() {
+static jboolean android_os_Binder_isDirectlyHandlingTransactionNative(CRITICAL_JNI_PARAMS) {
     return getCurrentServingCall() == BinderCallType::BINDER;
 }
 
-static jlong android_os_Binder_clearCallingIdentity()
+static jlong android_os_Binder_clearCallingIdentity(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->clearCallingIdentity();
 }
 
-static void android_os_Binder_restoreCallingIdentity(jlong token)
+static void android_os_Binder_restoreCallingIdentity(CRITICAL_JNI_PARAMS_COMMA jlong token)
 {
     IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
-static jboolean android_os_Binder_hasExplicitIdentity() {
+static jboolean android_os_Binder_hasExplicitIdentity(CRITICAL_JNI_PARAMS) {
     return IPCThreadState::self()->hasExplicitIdentity();
 }
 
-static void android_os_Binder_setThreadStrictModePolicy(jint policyMask)
+static void android_os_Binder_setThreadStrictModePolicy(CRITICAL_JNI_PARAMS_COMMA jint policyMask)
 {
     IPCThreadState::self()->setStrictModePolicy(policyMask);
 }
 
-static jint android_os_Binder_getThreadStrictModePolicy()
+static jint android_os_Binder_getThreadStrictModePolicy(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->getStrictModePolicy();
 }
 
-static jlong android_os_Binder_setCallingWorkSourceUid(jint workSource)
+static jlong android_os_Binder_setCallingWorkSourceUid(CRITICAL_JNI_PARAMS_COMMA jint workSource)
 {
     return IPCThreadState::self()->setCallingWorkSourceUid(workSource);
 }
 
-static jlong android_os_Binder_getCallingWorkSourceUid()
+static jlong android_os_Binder_getCallingWorkSourceUid(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->getCallingWorkSourceUid();
 }
 
-static jlong android_os_Binder_clearCallingWorkSource()
+static jlong android_os_Binder_clearCallingWorkSource(CRITICAL_JNI_PARAMS)
 {
     return IPCThreadState::self()->clearCallingWorkSource();
 }
 
-static void android_os_Binder_restoreCallingWorkSource(jlong token)
+static void android_os_Binder_restoreCallingWorkSource(CRITICAL_JNI_PARAMS_COMMA jlong token)
 {
     IPCThreadState::self()->restoreCallingWorkSource(token);
 }
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 5266214..e28b646 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -96,4 +96,8 @@
 
     <!-- True if the device supports system decorations on secondary displays. -->
     <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool>
+
+    <!-- Whether to enable scaling and fading animation to scrollviews while scrolling.
+         P.S this is a change only intended for wear devices. -->
+    <bool name="config_enableViewGroupScalingFading">true</bool>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 38aff75..f6267f6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7121,4 +7121,8 @@
     <!-- The maximum number of call log entries for each sim card that can be stored in the call log
          provider on the device. -->
     <integer name="config_maximumCallLogEntriesPerSim">500</integer>
+
+    <!-- Whether to enable scaling and fading animation to scrollviews while scrolling.
+         P.S this is a change only intended for wear devices. -->
+    <bool name="config_enableViewGroupScalingFading">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4693894..3c8c04e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5601,4 +5601,6 @@
 
   <!-- Fingerprint loe notification string -->
   <java-symbol type="string" name="fingerprint_loe_notification_msg" />
+
+  <java-symbol type="bool" name="config_enableViewGroupScalingFading"/>
 </resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
index 516253b..44beb55 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
@@ -19,18 +19,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
 
-import static com.google.common.truth.Truth.assertWithMessage;
-
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.app.compat.CompatChanges;
 import android.os.Binder;
 import android.os.UserHandle;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 
+import com.google.common.truth.Expect;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 
@@ -41,36 +41,40 @@
 
     private static final int USER_ID_1 = 11;
     private static final int USER_ID_2 = 12;
+    private RadioServiceUserController mUserController;
 
     @Mock
     private UserHandle mUserHandleMock;
 
+    @Rule
+    public final Expect expect = Expect.create();
+
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(ActivityManager.class).spyStatic(Binder.class)
-                .spyStatic(CompatChanges.class);
+        builder.spyStatic(ActivityManager.class).spyStatic(Binder.class);
     }
 
     @Before
     public void setUp() {
         doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
         doReturn(USER_ID_1).when(() -> ActivityManager.getCurrentUser());
+        mUserController = new RadioServiceUserControllerImpl();
     }
 
     @Test
     public void isCurrentOrSystemUser_forCurrentUser_returnsFalse() {
         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
 
-        assertWithMessage("Current user")
-                .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+        expect.withMessage("Current user")
+                .that(mUserController.isCurrentOrSystemUser()).isTrue();
     }
 
     @Test
     public void isCurrentOrSystemUser_forNonCurrentUser_returnsFalse() {
         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_2);
 
-        assertWithMessage("Non-current user")
-                .that(RadioServiceUserController.isCurrentOrSystemUser()).isFalse();
+        expect.withMessage("Non-current user")
+                .that(mUserController.isCurrentOrSystemUser()).isFalse();
     }
 
     @Test
@@ -78,8 +82,8 @@
         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
         when(mUserHandleMock.getIdentifier()).thenReturn(UserHandle.USER_SYSTEM);
 
-        assertWithMessage("System user")
-                .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+        expect.withMessage("System user")
+                .that(mUserController.isCurrentOrSystemUser()).isTrue();
     }
 
     @Test
@@ -87,14 +91,14 @@
         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
         doThrow(new RuntimeException()).when(ActivityManager::getCurrentUser);
 
-        assertWithMessage("User when activity manager fails")
-                .that(RadioServiceUserController.isCurrentOrSystemUser()).isFalse();
+        expect.withMessage("User when activity manager fails")
+                .that(mUserController.isCurrentOrSystemUser()).isFalse();
     }
 
     @Test
     public void getCurrentUser() {
-        assertWithMessage("Current user")
-                .that(RadioServiceUserController.getCurrentUser()).isEqualTo(USER_ID_1);
+        expect.withMessage("Current user")
+                .that(mUserController.getCurrentUser()).isEqualTo(USER_ID_1);
     }
 
     @Test
@@ -102,7 +106,15 @@
         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
         doThrow(new RuntimeException()).when(ActivityManager::getCurrentUser);
 
-        assertWithMessage("Current user when activity manager fails")
-                .that(RadioServiceUserController.getCurrentUser()).isEqualTo(UserHandle.USER_NULL);
+        expect.withMessage("Current user when activity manager fails")
+                .that(mUserController.getCurrentUser()).isEqualTo(UserHandle.USER_NULL);
+    }
+
+    @Test
+    public void getCallingUserId() {
+        when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
+
+        expect.withMessage("Calling user id")
+                .that(mUserController.getCallingUserId()).isEqualTo(USER_ID_1);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 22f3bd4..63f12d8 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -91,12 +91,13 @@
     private IAnnouncementListener mAnnouncementListenerMock;
     @Mock
     private IBinder mListenerBinderMock;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
         builder.spyStatic(ServiceManager.class)
-                .spyStatic(RadioModule.class)
-                .spyStatic(RadioServiceUserController.class);
+                .spyStatic(RadioModule.class);
     }
 
     @Test
@@ -156,7 +157,7 @@
     @Test
     public void openSession_forNonCurrentUser_throwsException() throws Exception {
         createBroadcastRadioService();
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
@@ -206,9 +207,9 @@
     }
 
     private void createBroadcastRadioService() throws RemoteException {
-        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(true).when(mUserControllerMock).isCurrentOrSystemUser();
         mockServiceManager();
-        mBroadcastRadioService = new BroadcastRadioServiceImpl(SERVICE_LIST);
+        mBroadcastRadioService = new BroadcastRadioServiceImpl(SERVICE_LIST, mUserControllerMock);
     }
 
     private void mockServiceManager() throws RemoteException {
@@ -222,9 +223,9 @@
                 any(IServiceCallback.class)));
 
         doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
-                eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class)));
+                eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class), any()));
         doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
-                eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class)));
+                eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class), any()));
 
         when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
         when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a952bde..368df09 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -34,6 +34,8 @@
 import android.os.ParcelableException;
 import android.os.RemoteException;
 
+import com.android.server.broadcastradio.RadioServiceUserController;
+
 import com.google.common.truth.Expect;
 
 import org.junit.Before;
@@ -63,6 +65,8 @@
     private IAnnouncementListener mListenerMock;
     @Mock
     private android.hardware.broadcastradio.ICloseHandle mHalCloseHandleMock;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     // RadioModule under test
     private RadioModule mRadioModule;
@@ -70,7 +74,8 @@
 
     @Before
     public void setup() throws RemoteException {
-        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES);
+        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES,
+                mUserControllerMock);
 
         // TODO(b/241118988): test non-null image for getImage method
         when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 92dfe38..24d18e0 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -49,12 +49,10 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
 import android.hardware.radio.UniqueProgramIdentifier;
-import android.os.Binder;
 import android.os.DeadObjectException;
 import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
-import android.os.UserHandle;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -148,10 +146,10 @@
 
     // Mocks
     @Mock
-    private UserHandle mUserHandleMock;
-    @Mock
     private IBroadcastRadio mBroadcastRadioMock;
     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     // RadioModule under test
     private RadioModule mRadioModule;
@@ -170,8 +168,7 @@
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class)
-                .spyStatic(Binder.class);
+        builder.spyStatic(CompatChanges.class);
     }
 
     @Before
@@ -182,13 +179,12 @@
 
         doReturn(true).when(() -> CompatChanges.isChangeEnabled(
                 eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
-        doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier();
-        doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
-        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
-        doReturn(USER_ID_1).when(() -> RadioServiceUserController.getCurrentUser());
+        doReturn(USER_ID_1).when(mUserControllerMock).getCallingUserId();
+        doReturn(true).when(mUserControllerMock).isCurrentOrSystemUser();
+        doReturn(USER_ID_1).when(mUserControllerMock).getCurrentUser();
 
         mRadioModule = new RadioModule(mBroadcastRadioMock,
-                AidlTestUtils.makeDefaultModuleProperties());
+                AidlTestUtils.makeDefaultModuleProperties(), mUserControllerMock);
 
         doAnswer(invocation -> {
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
@@ -237,7 +233,7 @@
     @Test
     public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
 
@@ -434,7 +430,7 @@
     @Test
     public void tune_forNonCurrentUser_doesNotTune() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
         RadioManager.ProgramInfo tuneInfo =
                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
@@ -514,7 +510,7 @@
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
 
@@ -593,7 +589,7 @@
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
 
@@ -627,7 +623,7 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].cancel();
 
@@ -698,7 +694,7 @@
     @Test
     public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].startBackgroundScan();
 
@@ -968,7 +964,7 @@
         openAidlClients(/* numClients= */ 1);
         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
                 /* includeCategories= */ true, /* excludeModifications= */ false);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].startProgramListUpdates(filter);
 
@@ -1007,7 +1003,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
                 /* includeCategories= */ true, /* excludeModifications= */ false);
         mTunerSessions[0].startProgramListUpdates(filter);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].stopProgramListUpdates();
 
@@ -1073,7 +1069,7 @@
     public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
 
@@ -1138,7 +1134,7 @@
         openAidlClients(/* numClients= */ 1);
         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
                 "mockParam2", "mockValue2");
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setParameters(parametersSet);
 
@@ -1192,7 +1188,7 @@
     public void onCurrentProgramInfoChanged_withNonCurrentUser_doesNotInvokeCallback()
             throws Exception {
         openAidlClients(1);
-        doReturn(USER_ID_2).when(() -> RadioServiceUserController.getCurrentUser());
+        doReturn(USER_ID_2).when(mUserControllerMock).getCurrentUser();
 
         mHalTunerCallback.onCurrentProgramInfoChanged(AidlTestUtils.makeHalProgramInfo(
                 AidlTestUtils.makeHalFmSelector(AM_FM_FREQUENCY_LIST[1]), SIGNAL_QUALITY));
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
index acf698b..7c3f221 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
@@ -88,11 +88,12 @@
     private IAnnouncementListener mAnnouncementListenerMock;
     @Mock
     private IBinder mBinderMock;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(RadioModule.class)
-                .spyStatic(RadioServiceUserController.class);
+        builder.spyStatic(RadioModule.class);
     }
 
     @Test
@@ -156,7 +157,7 @@
     @Test
     public void openSession_forNonCurrentUser_throwsException() throws Exception {
         createBroadcastRadioService();
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        when(mUserControllerMock.isCurrentOrSystemUser()).thenReturn(false);
 
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
@@ -206,11 +207,11 @@
     }
 
     private void createBroadcastRadioService() throws RemoteException {
-        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        when(mUserControllerMock.isCurrentOrSystemUser()).thenReturn(true);
 
         mockServiceManager();
         mBroadcastRadioService = new BroadcastRadioService(/* nextModuleId= */ FM_RADIO_MODULE_ID,
-                mServiceManagerMock);
+                mServiceManagerMock, mUserControllerMock);
     }
 
     private void mockServiceManager() throws RemoteException {
@@ -231,9 +232,9 @@
                 }).thenReturn(true);
 
         doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
-                eq(FM_RADIO_MODULE_ID), anyString()));
+                eq(FM_RADIO_MODULE_ID), anyString(), any()));
         doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
-                eq(DAB_RADIO_MODULE_ID), anyString()));
+                eq(DAB_RADIO_MODULE_ID), anyString(), any()));
 
         when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
         when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
index 1f5e770..b53f7ca 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
@@ -36,6 +36,8 @@
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
 
+import com.android.server.broadcastradio.RadioServiceUserController;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,13 +63,16 @@
     private IAnnouncementListener mListenerMock;
     @Mock
     private android.hardware.broadcastradio.V2_0.ICloseHandle mHalCloseHandleMock;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     private RadioModule mRadioModule;
     private android.hardware.broadcastradio.V2_0.IAnnouncementListener mHalListener;
 
     @Before
     public void setup() throws RemoteException {
-        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES);
+        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES,
+                mUserControllerMock);
 
         when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 8c16d79..fa07447 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -38,13 +38,13 @@
 import android.hardware.radio.UniqueProgramIdentifier;
 import android.os.RemoteException;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
 import com.android.server.broadcastradio.RadioServiceUserController;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.mockito.verification.VerificationWithTimeout;
 
@@ -55,7 +55,8 @@
 /**
  * Tests for v2 HAL RadioModule.
  */
-public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestCase {
+@RunWith(MockitoJUnitRunner.class)
+public class StartProgramListUpdatesFanoutTest {
     private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout";
 
     private static final VerificationWithTimeout CB_TIMEOUT = timeout(500);
@@ -64,6 +65,8 @@
     @Mock IBroadcastRadio mBroadcastRadioMock;
     @Mock ITunerSession mHalTunerSessionMock;
     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     // RadioModule under test
     private RadioModule mRadioModule;
@@ -110,17 +113,12 @@
     private static final RadioManager.ProgramInfo TEST_DAB_INFO = TestUtils.makeProgramInfo(
             TEST_DAB_SELECTOR, TEST_QUALITY);
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(RadioServiceUserController.class);
-    }
-
     @Before
     public void setup() throws RemoteException {
-        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(true).when(mUserControllerMock).isCurrentOrSystemUser();
 
-        mRadioModule = new RadioModule(mBroadcastRadioMock,
-                TestUtils.makeDefaultModuleProperties());
+        mRadioModule = new RadioModule(mBroadcastRadioMock, TestUtils.makeDefaultModuleProperties(),
+                mUserControllerMock);
 
         doAnswer((Answer) invocation -> {
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index 55aae9d..62445cf 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -44,16 +44,12 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
-import android.os.Binder;
 import android.os.DeadObjectException;
 import android.os.ParcelableException;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
 import com.android.server.broadcastradio.RadioServiceUserController;
 
 import com.google.common.truth.Expect;
@@ -76,7 +72,7 @@
  * Tests for HIDL HAL TunerSession.
  */
 @RunWith(MockitoJUnitRunner.class)
-public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase {
+public final class TunerSessionHidlTest {
 
     private static final int USER_ID_1 = 11;
     private static final int USER_ID_2 = 12;
@@ -104,27 +100,21 @@
     public final Expect mExpect = Expect.create();
 
     @Mock
-    private UserHandle mUserHandleMock;
-    @Mock
     private IBroadcastRadio mBroadcastRadioMock;
     @Mock
     ITunerSession mHalTunerSessionMock;
     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
-
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(RadioServiceUserController.class).spyStatic(Binder.class);
-    }
+    @Mock
+    private RadioServiceUserController mUserControllerMock;
 
     @Before
     public void setup() throws Exception {
-        doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier();
-        doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
-        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
-        doReturn(USER_ID_1).when(() -> RadioServiceUserController.getCurrentUser());
+        doReturn(USER_ID_1).when(mUserControllerMock).getCallingUserId();
+        doReturn(true).when(mUserControllerMock).isCurrentOrSystemUser();
+        doReturn(USER_ID_1).when(mUserControllerMock).getCurrentUser();
 
         mRadioModule = new RadioModule(mBroadcastRadioMock,
-                TestUtils.makeDefaultModuleProperties());
+                TestUtils.makeDefaultModuleProperties(), mUserControllerMock);
 
         doAnswer(invocation -> {
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
@@ -228,7 +218,7 @@
     @Test
     public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
 
@@ -403,7 +393,7 @@
     @Test
     public void tune_forNonCurrentUser_doesNotTune() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
         ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
         RadioManager.ProgramInfo tuneInfo =
                 TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
@@ -481,7 +471,7 @@
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
 
@@ -559,7 +549,7 @@
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
 
@@ -593,7 +583,7 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].cancel();
 
@@ -663,7 +653,7 @@
     @Test
     public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].startBackgroundScan();
 
@@ -676,7 +666,7 @@
         openAidlClients(/* numClients= */ 1);
         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
                 /* includeCategories= */ true, /* excludeModifications= */ false);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].startProgramListUpdates(filter);
 
@@ -715,7 +705,7 @@
         ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
                 /* includeCategories= */ true, /* excludeModifications= */ false);
         mTunerSessions[0].startProgramListUpdates(aidlFilter);
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].stopProgramListUpdates();
 
@@ -781,7 +771,7 @@
     public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
 
@@ -846,7 +836,7 @@
         openAidlClients(/* numClients= */ 1);
         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
                 "mockParam2", "mockValue2");
-        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        doReturn(false).when(mUserControllerMock).isCurrentOrSystemUser();
 
         mTunerSessions[0].setParameters(parametersSet);
 
@@ -900,7 +890,7 @@
     public void onCurrentProgramInfoChanged_withNonCurrentUser_doesNotInvokeCallback()
             throws Exception {
         openAidlClients(1);
-        doReturn(USER_ID_2).when(() -> RadioServiceUserController.getCurrentUser());
+        doReturn(USER_ID_2).when(mUserControllerMock).getCurrentUser();
 
         mHalTunerCallback.onCurrentProgramInfoChanged(TestUtils.makeHalProgramInfo(
                 TestUtils.makeHalFmSelector(/* freq= */ 97300), SIGNAL_QUALITY));
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 99cbf05..41599ae 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -299,11 +299,6 @@
     auto_gen_config: true,
 }
 
-FLAKY_OR_IGNORED = [
-    "androidx.test.filters.FlakyTest",
-    "org.junit.Ignore",
-]
-
 test_module_config {
     name: "FrameworksCoreTests_Presubmit",
     base: "FrameworksCoreTests",
@@ -312,7 +307,6 @@
         "device-platinum-tests",
     ],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -337,7 +331,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.content.ContextTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -348,7 +341,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.app.KeyguardManagerTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -359,7 +351,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.app.PropertyInvalidatedCacheTests"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -374,7 +365,6 @@
         "android.content.ComponentCallbacksControllerTest",
         "android.content.ContextWrapperTest",
     ],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -385,7 +375,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.database.sqlite.SQLiteRawStatementTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -397,7 +386,6 @@
     ],
     include_filters: ["android.net"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -510,7 +498,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["com.android.internal.jank"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -569,7 +556,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["com.android.internal.util.LatencyTrackerTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -580,7 +566,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.content.ContentCaptureOptionsTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -602,7 +587,6 @@
     ],
     include_filters: ["android.content.pm."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -614,7 +598,6 @@
     ],
     include_filters: ["android.content.pm."],
     include_annotations: ["android.platform.test.annotations.Postsubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -642,7 +625,6 @@
     ],
     include_filters: ["android.content.res."],
     include_annotations: ["android.platform.test.annotations.Postsubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -672,7 +654,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.view.contentcapture"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -683,7 +664,6 @@
         "device-platinum-tests",
     ],
     include_filters: ["android.view.contentprotection"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -695,7 +675,6 @@
     ],
     include_filters: ["com.android.internal.content."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -709,21 +688,6 @@
 }
 
 test_module_config {
-    name: "FrameworksCoreTests_accessibility_NO_FLAKES",
-    base: "FrameworksCoreTests",
-    test_suites: [
-        "device-tests",
-        "device-platinum-tests",
-    ],
-    include_filters: [
-        "com.android.internal.accessibility",
-        "android.accessibilityservice",
-        "android.view.accessibility",
-    ],
-    exclude_annotations: ["androidx.test.filters.FlakyTest"],
-}
-
-test_module_config {
     name: "FrameworksCoreTests_accessibility",
     base: "FrameworksCoreTests",
     test_suites: [
@@ -792,7 +756,6 @@
         "com.android.internal.jank.InteractionJankMonitorTest",
         "com.android.internal.util.LatencyTrackerTest",
     ],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
@@ -803,7 +766,6 @@
         "device-platinum-tests",
     ],
     include_annotations: ["android.platform.test.annotations.PlatinumTest"],
-    exclude_annotations: FLAKY_OR_IGNORED,
 }
 
 test_module_config {
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index e47ef2d..e19f887 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -47,12 +47,13 @@
 import android.os.VibrationEffect;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.UsesFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.test.mock.MockContentResolver;
 import android.util.Xml;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -61,6 +62,7 @@
 import com.google.common.base.Strings;
 
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,14 +73,28 @@
 import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
 
-@RunWith(AndroidJUnit4.class)
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
+@UsesFlags(android.app.Flags.class)
 @SmallTest
 @Presubmit
 public class NotificationChannelTest {
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_NOTIF_CHANNEL_CROP_VIBRATION_EFFECTS);
+    }
+
     @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    public final SetFlagsRule mSetFlagsRule;
 
     private final String CLASS = "android.app.NotificationChannel";
 
@@ -86,6 +102,10 @@
     ContentProvider mContentProvider;
     IContentProvider mIContentProvider;
 
+    public NotificationChannelTest(FlagsParameterization flags) {
+        mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         mContext = mock(Context.class);
diff --git a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
index e19c4b1..3616ff5c 100644
--- a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
@@ -33,71 +33,169 @@
 public class LauncherActivityInfoTest {
 
     @Test
-    public void testTrimStart() {
-        // Invisible case
-        assertThat(LauncherActivityInfo.trimStart("\u0009").toString()).isEmpty();
-        // It is not supported in the system font
-        assertThat(LauncherActivityInfo.trimStart("\u0FE1").toString()).isEmpty();
-        // Surrogates case
-        assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36").toString())
-                .isEqualTo("\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trimStart("\u0009\u0FE1\uD83E\uDD36A").toString())
-                .isEqualTo("\uD83E\uDD36A");
-        assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
-        assertThat(LauncherActivityInfo.trimStart("A\uD83E\uDD36\u0009\u0FE1A").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
-        assertThat(LauncherActivityInfo.trimStart(
-                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trimStart(
-                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
+    public void testIsVisible_normal() {
+        // normal
+        assertThat(LauncherActivityInfo.isVisible("label")).isTrue();
+        // 1 surrogates case
+        assertThat(LauncherActivityInfo.isVisible("\uD83E\uDD36")).isTrue();
     }
 
     @Test
-    public void testTrimEnd() {
-        // Invisible case
-        assertThat(LauncherActivityInfo.trimEnd("\u0009").toString()).isEmpty();
-        // It is not supported in the system font
-        assertThat(LauncherActivityInfo.trimEnd("\u0FE1").toString()).isEmpty();
-        // Surrogates case
-        assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36").toString())
-                .isEqualTo("\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trimEnd("\u0009\u0FE1\uD83E\uDD36A").toString())
-                .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
-        assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\uD83E\uDD36A");
-        assertThat(LauncherActivityInfo.trimEnd("A\uD83E\uDD36\u0009\u0FE1A").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
-        assertThat(LauncherActivityInfo.trimEnd(
-                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trimEnd(
-                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
+    public void testIsVisible_onlyInvisibleCharacter() {
+        // 1 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u0009")).isFalse();
+        // 2 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164")).isFalse();
+        // 3 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u3000\u0009\u3164")).isFalse();
+        // 4 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u200F\u3000\u0009\u3164")).isFalse();
     }
 
     @Test
-    public void testTrim() {
-        // Invisible case
-        assertThat(LauncherActivityInfo.trim("\u0009").toString()).isEmpty();
-        // It is not supported in the system font
-        assertThat(LauncherActivityInfo.trim("\u0FE1").toString()).isEmpty();
-        // Surrogates case
-        assertThat(LauncherActivityInfo.trim("\uD83E\uDD36").toString())
-                .isEqualTo("\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trim("\u0009\u0FE1\uD83E\uDD36A").toString())
-                .isEqualTo("\uD83E\uDD36A");
-        assertThat(LauncherActivityInfo.trim("\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\uD83E\uDD36A");
-        assertThat(LauncherActivityInfo.trim("A\uD83E\uDD36\u0009\u0FE1A").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
-        assertThat(LauncherActivityInfo.trim(
-                "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
-                .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
-        assertThat(LauncherActivityInfo.trim(
-                "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
-                .isEqualTo("\uD83E\uDD36A");
+    public void testIsVisible_onlyNotSupportedCharacter() {
+        // 1 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1")).isFalse();
+        // 2 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0FE2")).isFalse();
+        // 3 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0FE2\u0FE3")).isFalse();
+        // 4 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0FE2\u0FE3\u0FE4")).isFalse();
+    }
+
+    @Test
+    public void testIsVisible_invisibleAndNotSupportedCharacter() {
+        // 1 invisible, 1 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1")).isFalse();
+        // 1 invisible, 2 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1\u0FE2")).isFalse();
+        // 1 invisible, 3 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1\u0FE2\u0FE3")).isFalse();
+        // 1 invisible, 4 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1\u0FE2\u0FE3\u0FE4")).isFalse();
+
+        // 2 invisible, 1 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164\u0FE1")).isFalse();
+        // 2 invisible, 2 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164\u0FE1\u0FE2")).isFalse();
+        // 2 invisible, 3 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164\u0FE1\u0FE2\u0FE3")).isFalse();
+        // 2 invisible, 4 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0009\u3164\u0FE1\u0FE2\u0FE3\u0FE4")).isFalse();
+
+        // 3 invisible, 1 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u3000\u0009\u3164\u0FE1")).isFalse();
+        // 3 invisible, 2 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u3000\u0009\u3164\u0FE1\u0FE2")).isFalse();
+        // 3 invisible, 3 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3")).isFalse();
+        // 3 invisible, 4 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3\u0FE4")).isFalse();
+
+        // 4 invisible, 1 not supported
+        assertThat(LauncherActivityInfo.isVisible("\u200F\u3000\u0009\u3164\u0FE1")).isFalse();
+        // 4 invisible, 2 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2")).isFalse();
+        // 4 invisible, 3 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3")).isFalse();
+        // 4 invisible, 4 not supported
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3\u0FE4")).isFalse();
+
+        // 1 not supported, 1 invisible,
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0009")).isFalse();
+        // 1 not supported, 2 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0009\u3164")).isFalse();
+        // 1 not supported, 3 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u3000\u0009\u3164")).isFalse();
+        // 1 not supported, 4 invisible
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u200F\u3000\u0009\u3164")).isFalse();
+    }
+
+    @Test
+    public void testIsVisible_invisibleAndNormalCharacter() {
+        // 1 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0009\uD83E\uDD36")).isTrue();
+        // 2 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164\uD83E\uDD36")).isTrue();
+        // 3 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u3000\u0009\u3164\uD83E\uDD36")).isFalse();
+        // 4 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\uD83E\uDD36")).isFalse();
+    }
+
+    @Test
+    public void testIsVisible_notSupportedAndNormalCharacter() {
+        // 1 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\uD83E\uDD36")).isTrue();
+        // 2 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0FE2\uD83E\uDD36")).isTrue();
+        // 3 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0FE2\u0FE3\uD83E\uDD36")).isTrue();
+        // 4 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0FE1\u0FE2\u0FE3\u0FE4\uD83E\uDD36")).isTrue();
+    }
+
+    @Test
+    public void testIsVisible_mixAllCharacter() {
+        // 1 invisible, 1 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1\uD83E\uDD36")).isTrue();
+        // 1 invisible, 1 not supported, 1 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u0FE1\u3164\uD83E\uDD36")).isTrue();
+        // 1 invisible, 1 not supported, 2 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0009\u0FE1\u3000\u3164\uD83E\uDD36")).isTrue();
+        // 1 invisible, 1 not supported, 3 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0009\u0FE1\u200F\u3000\u3164\uD83E\uDD36")).isTrue();
+
+        // 2 invisible, 1 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0009\u3164\u0FE1\uD83E\uDD36")).isTrue();
+        // 2 invisible, 2 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0009\u3164\u0FE1\u0FE2\uD83E\uDD36")).isTrue();
+
+        // 3 invisible, 1 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u3000\u0009\u3164\u0FE1\uD83E\uDD36")).isFalse();
+        // 3 invisible, 2 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u3000\u0009\u3164\u0FE1\u0FE2\uD83E\uDD36")).isFalse();
+        // 3 invisible, 3 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3\uD83E\uDD36")).isFalse();
+
+        // 4 invisible, 1 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\uD83E\uDD36")).isFalse();
+        // 4 invisible, 2 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2\uD83E\uDD36")).isFalse();
+        // 4 invisible, 3 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3\uD83E\uDD36")).isFalse();
+        // 4 invisible, 4 not supported, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u200F\u3000\u0009\u3164\u0FE1\u0FE2\u0FE3\u0FE4\uD83E\uDD36")).isFalse();
+
+        // 1 not supported, 1 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0009\uD83E\uDD36")).isTrue();
+        // 1 not supported, 2 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible("\u0FE1\u0009\u3164\uD83E\uDD36")).isTrue();
+        // 1 not supported, 3 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0FE1\u3000\u0009\u3164\uD83E\uDD36")).isTrue();
+        // 1 not supported, 4 invisible, 1 surrogates
+        assertThat(LauncherActivityInfo.isVisible(
+                "\u0FE1\u200F\u3000\u0009\u3164\uD83E\uDD36")).isTrue();
+
     }
 }
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 9767d67..90ec93e 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -24,18 +24,16 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.testng.Assert.assertThrows;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.os.BinderInternal;
 
-
 import org.junit.Rule;
 import org.junit.Test;
 
-@IgnoreUnderRavenwood(blockedBy = WorkSource.class)
 public class BinderTest {
     private static final int UID = 100;
 
@@ -89,6 +87,7 @@
 
     @SmallTest
     @Test(expected = java.lang.SecurityException.class)
+    @DisabledOnRavenwood(blockedBy = ServiceManagerNative.class)
     public void testServiceManagerNativeSecurityException() throws RemoteException {
         // Find the service manager
         IServiceManager sServiceManager = ServiceManagerNative
@@ -101,6 +100,7 @@
 
     @SmallTest
     @Test(expected = java.lang.NullPointerException.class)
+    @DisabledOnRavenwood(blockedBy = ServiceManagerNative.class)
     public void testServiceManagerNativeNullptrException() throws RemoteException {
         // Find the service manager
         IServiceManager sServiceManager = ServiceManagerNative
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index ded6fc5..31e0752 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
@@ -131,7 +130,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
     public void testCreateFromParcel() throws Exception {
         boolean withFd;
         Parcel p;
@@ -312,7 +310,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+    @DisabledOnRavenwood(reason = "Ravenwood tests run on the BCP")
     public void kindofEquals_lazyValuesAndDifferentClassLoaders_returnsFalse() {
         Parcelable p1 = new CustomParcelable(13, "Tiramisu");
         Parcelable p2 = new CustomParcelable(13, "Tiramisu");
@@ -368,7 +366,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void readWriteLengthMismatch_logsWtf() throws Exception {
         mWtfHandler = Log.setWtfHandler((tag, e, system) -> {
             throw new RuntimeException(e);
@@ -383,7 +380,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+    @DisabledOnRavenwood(reason = "Ravenwood tests run on the BCP")
     public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception {
         Bundle.setShouldDefuse(false);
         Bundle bundle = new Bundle();
@@ -396,7 +393,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+    @DisabledOnRavenwood(reason = "Ravenwood tests run on the BCP")
     public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception {
         Bundle.setShouldDefuse(true);
         Bundle bundle = new Bundle();
@@ -412,7 +409,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+    @DisabledOnRavenwood(reason = "Ravenwood tests run on the BCP")
     public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception {
         Bundle.setShouldDefuse(true);
         Bundle bundle = new Bundle();
@@ -447,7 +444,6 @@
     }
 
     @Test
-    @DisabledOnRavenwood(blockedBy = Parcel.class)
     public void parcelledBundleWithBinder_shouldReturnHasBindersTrue() throws Exception {
         Bundle bundle = new Bundle();
         bundle.putParcelable("test", new CustomParcelable(13, "Tiramisu"));
@@ -470,7 +466,6 @@
     }
 
     @Test
-    @DisabledOnRavenwood(blockedBy = Parcel.class)
     public void parcelledBundleWithoutBinder_shouldReturnHasBindersFalse() throws Exception {
         Bundle bundle = new Bundle();
         bundle.putParcelable("test", new CustomParcelable(13, "Tiramisu"));
diff --git a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
index 09395f1..96316c4 100644
--- a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
+++ b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
@@ -20,7 +20,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArrayMap;
 
@@ -67,7 +67,7 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
+    @DisabledOnRavenwood(blockedBy = android.text.Spanned.class)
     public void nullCharSequence() {
         Parcel p = Parcel.obtain();
         p.writeCharSequence(null);
@@ -76,7 +76,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void nullStrongBinder() {
         Parcel p = Parcel.obtain();
         p.writeStrongBinder(null);
@@ -85,7 +84,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void nullStringInterface() {
         Parcel p = Parcel.obtain();
         p.writeStrongInterface(null);
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 0373231..da9d687 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
@@ -48,7 +47,6 @@
     private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token";
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testIsForRpc() {
         Parcel p = Parcel.obtain();
         assertEquals(false, p.isForRpc());
@@ -56,7 +54,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCallingWorkSourceUidAfterWrite() {
         Parcel p = Parcel.obtain();
         // Method does not throw if replaceCallingWorkSourceUid is called before requests headers
@@ -77,7 +74,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCallingWorkSourceUidAfterEnforce() {
         Parcel p = Parcel.obtain();
         p.writeInterfaceToken(INTERFACE_TOKEN_1);
@@ -95,7 +91,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testParcelWithMultipleHeaders() {
         Parcel p = Parcel.obtain();
         Binder.setCallingWorkSourceUid(WORK_SOURCE_1);
@@ -153,7 +148,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenSameDataWithBinder() {
         Binder binder = new Binder();
         Parcel pA = Parcel.obtain();
@@ -313,7 +307,6 @@
      * and 1M length for complex objects are allowed.
      */
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testAllocations_whenWithinLimit() {
         Binder.setIsDirectlyHandlingTransactionOverride(true);
         Parcel p = Parcel.obtain();
@@ -398,7 +391,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testHasBinders_AfterWritingBinderToParcel() {
         Binder binder = new Binder();
         Parcel pA = Parcel.obtain();
@@ -410,7 +402,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testHasBindersInRange_AfterWritingBinderToParcel() {
         Binder binder = new Binder();
         Parcel pA = Parcel.obtain();
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 71980c1..e4e04a0 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -17,11 +17,9 @@
 package android.text
 
 import android.graphics.Paint
-import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -40,7 +38,6 @@
     @JvmField
     val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun calculateRunFlagTest() {
         // Only one Bidi run
@@ -84,7 +81,6 @@
                 .isEqualTo(LEFT_EDGE)
     }
 
-    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun resolveRunFlagForSubSequenceTest() {
         val runStart = 5
diff --git a/core/tests/coretests/src/com/android/internal/widget/ViewGroupFaderTest.java b/core/tests/coretests/src/com/android/internal/widget/ViewGroupFaderTest.java
new file mode 100644
index 0000000..eeabc2f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/ViewGroupFaderTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.flags.Flags;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link ViewGroupFader}.
+ */
+public class ViewGroupFaderTest extends AndroidTestCase {
+
+    private Context mContext;
+    private ViewGroupFader mViewGroupFader;
+    private Resources mResources;
+
+    @Mock
+    private ViewGroup mViewGroup,mViewGroup1;
+
+    @Mock
+    private ViewGroupFader mockViewGroupFader;
+
+    @Mock
+    private ViewGroupFader.AnimationCallback mAnimationCallback;
+
+    @Mock
+    private ViewGroupFader.ChildViewBoundsProvider mChildViewBoundsProvider;
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        final Context mContext = getInstrumentation().getContext();
+        mResources = spy(mContext.getResources());
+        when(mResources.getBoolean(com.android.internal.R.bool.config_enableViewGroupScalingFading))
+                .thenReturn(true);
+        when(mViewGroup.getResources()).thenReturn(mResources);
+
+        mViewGroupFader = new ViewGroupFader(
+                mViewGroup,
+                mAnimationCallback,
+                mChildViewBoundsProvider);
+    }
+
+    /** This test checks that for each child of the parent viewgroup,
+     * updateListElementFades is called for each of its child, when the Flag is set to true
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FADING_VIEW_GROUP)
+    public void testFadingAndScrollingAnimationWorking_FlagOn() {
+        mViewGroup.addView(mViewGroup1);
+        mViewGroupFader.updateFade();
+
+        for (int i = 0; i < mViewGroup.getChildCount(); i++) {
+            View child = mViewGroup.getChildAt(i);
+            verify(mockViewGroupFader).updateListElementFades((ViewGroup)child,true);
+        }
+    }
+
+    /** This test checks that for each child of the parent viewgroup,
+     * updateListElementFades is never called for each of its child, when the Flag is set to false
+     */
+    @Test
+    public void testFadingAndScrollingAnimationNotWorking_FlagOff() {
+        mViewGroup.addView(mViewGroup1);
+        mViewGroupFader.updateFade();
+
+        for (int i = 0; i < mViewGroup.getChildCount(); i++) {
+            View child = mViewGroup.getChildAt(i);
+            verify(mockViewGroupFader,never()).updateListElementFades((ViewGroup)child,true);
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS
index 9fa8f1b..ef8d26c 100644
--- a/graphics/java/android/graphics/OWNERS
+++ b/graphics/java/android/graphics/OWNERS
@@ -11,3 +11,4 @@
 per-file FontFamily.java = file:fonts/OWNERS
 per-file FontListParser.java = file:fonts/OWNERS
 per-file Typeface.java = file:fonts/OWNERS
+per-file Paint.java = file:fonts/OWNERS
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index df95a91..b866382 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1805,16 +1805,7 @@
      * @return true if elegant metrics are enabled for text drawing.
      */
     public boolean isElegantTextHeight() {
-        int rawValue = nGetElegantTextHeight(mNativePaint);
-        switch (rawValue) {
-            case ELEGANT_TEXT_HEIGHT_DISABLED:
-                return false;
-            case ELEGANT_TEXT_HEIGHT_ENABLED:
-                return true;
-            case ELEGANT_TEXT_HEIGHT_UNSET:
-            default:
-                return com.android.text.flags.Flags.deprecateUiFonts();
-        }
+        return nGetElegantTextHeight(mNativePaint) != ELEGANT_TEXT_HEIGHT_DISABLED;
     }
 
     // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index f727f5b..0e25c34 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -306,13 +306,7 @@
             long lastModifiedDate,
             int configVersion
     ) {
-        final String fontsXml;
-        if (com.android.text.flags.Flags.newFontsFallbackXml()) {
-            fontsXml = FONTS_XML;
-        } else {
-            fontsXml = LEGACY_FONTS_XML;
-        }
-        return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
+        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
                 updatableFontMap, lastModifiedDate, configVersion);
     }
 
@@ -337,13 +331,7 @@
      * @hide
      */
     public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
-        final String fontsXml;
-        if (com.android.text.flags.Flags.newFontsFallbackXml()) {
-            fontsXml = FONTS_XML;
-        } else {
-            fontsXml = LEGACY_FONTS_XML;
-        }
-        return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
+        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
                 0, 0);
     }
 
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2d98a2b..755e0d5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -569,7 +569,7 @@
     <!-- The thickness in dp for all desktop drag transition regions. -->
     <dimen name="desktop_mode_transition_region_thickness">44dp</dimen>
 
-    <item type="dimen" format="float" name="desktop_mode_fullscreen_region_scale">0.4</item>
+    <item type="dimen" format="float" name="desktop_mode_fullscreen_region_scale">0.2</item>
 
     <!-- The height on the screen where drag to the left or right edge will result in a
     desktop task snapping to split size. The empty space between this and the top is to allow
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 424d4bf..b5d63bd 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -49,6 +49,7 @@
     SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
     DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
     DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false),
+    SCALED_RESIZING(Flags::enableWindowingScaledResizing, false),
     ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
     BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
     EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index bfc0ee8..7261919 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -45,6 +45,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.policy.SystemBarUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -173,8 +174,7 @@
         final Region region = new Region();
         int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                 || mDragStartState == DragStartState.DRAGGED_INTENT
-                ? mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness)
+                ? SystemBarUtils.getStatusBarHeight(mContext)
                 : 2 * layout.stableInsets().top;
         // A Rect at the top of the screen that takes up the center 40%.
         if (mDragStartState == DragStartState.FROM_FREEFORM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 1ffa541..832e2d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -118,7 +118,7 @@
 
     @Override
     public IBinder startMinimizedModeTransition(WindowContainerTransaction wct) {
-        final int type = WindowManager.TRANSIT_TO_BACK;
+        final int type = Transitions.TRANSIT_MINIMIZE;
         final IBinder token = mTransitions.startTransition(type, wct, this);
         mPendingTransitionTokens.add(token);
         return token;
@@ -161,7 +161,8 @@
                             transition, info.getType(), change);
                     break;
                 case WindowManager.TRANSIT_TO_BACK:
-                    transitionHandled |= startMinimizeTransition(transition);
+                    transitionHandled |= startMinimizeTransition(
+                            transition, info.getType(), change);
                     break;
                 case WindowManager.TRANSIT_CLOSE:
                     if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
@@ -227,8 +228,20 @@
         return handled;
     }
 
-    private boolean startMinimizeTransition(IBinder transition) {
-        return mPendingTransitionTokens.contains(transition);
+    private boolean startMinimizeTransition(
+            IBinder transition,
+            int type,
+            TransitionInfo.Change change) {
+        if (!mPendingTransitionTokens.contains(transition)) {
+            return false;
+        }
+
+        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+        if (type != Transitions.TRANSIT_MINIMIZE) {
+            return false;
+        }
+        // TODO(b/361524575): Add minimize animations
+        return true;
     }
 
     private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4df649c..f060158 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -497,13 +497,8 @@
             mCurrentValue = value;
         }
 
-        boolean shouldApplyCornerRadius() {
-            return !isOutPipDirection(mTransitionDirection);
-        }
-
         boolean shouldApplyShadowRadius() {
-            return !isOutPipDirection(mTransitionDirection)
-                    && !isRemovePipDirection(mTransitionDirection);
+            return !isRemovePipDirection(mTransitionDirection);
         }
 
         boolean inScaleTransition() {
@@ -556,7 +551,7 @@
                     final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
                     setCurrentValue(alpha);
                     getSurfaceTransactionHelper().alpha(tx, leash, alpha)
-                            .round(tx, leash, shouldApplyCornerRadius())
+                            .round(tx, leash, true /* applyCornerRadius */)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) {
                         tx.apply();
@@ -572,7 +567,7 @@
                     getSurfaceTransactionHelper()
                             .resetScale(tx, leash, getDestinationBounds())
                             .crop(tx, leash, getDestinationBounds())
-                            .round(tx, leash, shouldApplyCornerRadius())
+                            .round(tx, leash, true /* applyCornerRadius */)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     tx.show(leash);
                     tx.apply();
@@ -686,13 +681,11 @@
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 adjustedSourceRectHint, initialSourceValue, bounds, insets,
                                 isInPipDirection, fraction);
-                        if (shouldApplyCornerRadius()) {
-                            final Rect sourceBounds = new Rect(initialContainerRect);
-                            sourceBounds.inset(insets);
-                            getSurfaceTransactionHelper()
-                                    .round(tx, leash, sourceBounds, bounds)
-                                    .shadow(tx, leash, shouldApplyShadowRadius());
-                        }
+                        final Rect sourceBounds = new Rect(initialContainerRect);
+                        sourceBounds.inset(insets);
+                        getSurfaceTransactionHelper()
+                                .round(tx, leash, sourceBounds, bounds)
+                                .shadow(tx, leash, shouldApplyShadowRadius());
                     }
                     if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) {
                         tx.apply();
@@ -741,11 +734,9 @@
                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
                                     insets, degree, x, y, isOutPipDirection,
                                     rotationDelta == ROTATION_270 /* clockwise */);
-                    if (shouldApplyCornerRadius()) {
-                        getSurfaceTransactionHelper()
-                                .round(tx, leash, sourceBounds, bounds)
-                                .shadow(tx, leash, shouldApplyShadowRadius());
-                    }
+                    getSurfaceTransactionHelper()
+                            .round(tx, leash, sourceBounds, bounds)
+                            .shadow(tx, leash, shouldApplyShadowRadius());
                     if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
                         tx.apply();
                     }
@@ -761,7 +752,7 @@
                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
                     getSurfaceTransactionHelper()
                             .alpha(tx, leash, 1f)
-                            .round(tx, leash, shouldApplyCornerRadius())
+                            .round(tx, leash, true /* applyCornerRadius */)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     tx.show(leash);
                     tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 1e6fa28..2033902 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -24,6 +24,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_REQUEST;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
@@ -45,6 +46,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -211,6 +213,8 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
             case EXIT_REASON_DESKTOP_MODE:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
+            case EXIT_REASON_FULLSCREEN_REQUEST:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_REQUEST;
             case EXIT_REASON_UNKNOWN:
                 // Fall through
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index dad0d4e..1b143eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1175,6 +1175,7 @@
         mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
         setSplitsVisible(false);
         mBreakOnNextWake = false;
+        logExit(reason);
     }
 
     void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -1265,6 +1266,7 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareExitSplitScreen(stage, wct);
         mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+        logExit(exitReason);
     }
 
     /**
@@ -1361,6 +1363,7 @@
             mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
             mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
         });
+        logExit(exitReason);
     }
 
     /**
@@ -1579,7 +1582,7 @@
         if (stage == STAGE_TYPE_MAIN) {
             mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                     mSplitLayout.isLeftRightSplit());
-        } else {
+        } else if (stage == STAGE_TYPE_SIDE) {
             mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                     mSplitLayout.isLeftRightSplit());
         }
@@ -2275,6 +2278,7 @@
         if (isOpening && inFullscreen) {
             // One task is opening into fullscreen mode, remove the corresponding split record.
             mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
+            logExit(EXIT_REASON_FULLSCREEN_REQUEST);
         }
 
         if (isSplitActive()) {
@@ -2402,6 +2406,7 @@
             if (triggerTask != null) {
                 mRecentTasks.ifPresent(
                         recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
+                logExit(EXIT_REASON_CHILD_TASK_ENTER_PIP);
             }
             @StageType int topStage = STAGE_TYPE_UNDEFINED;
             if (isSplitScreenVisible()) {
@@ -2743,7 +2748,10 @@
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 pendingEnter.cancel(
-                        (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+                        (cancelWct, cancelT) -> {
+                            prepareExitSplitScreen(dismissTop, cancelWct);
+                            logExit(EXIT_REASON_UNKNOWN);
+                        });
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "launched 2 tasks in split, but didn't receive "
                         + "2 tasks in transition. Possibly one of them failed to launch"));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 7dc336b..aba8b61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -196,6 +196,9 @@
     /** Transition to set windowing mode after exit pip transition is finished animating. */
     public static final int TRANSIT_CLEANUP_PIP_EXIT = WindowManager.TRANSIT_FIRST_CUSTOM + 19;
 
+    /** Transition type to minimize a task. */
+    public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20;
+
     /** Transition type for desktop mode transitions. */
     public static final int TRANSIT_DESKTOP_MODE_TYPES =
             WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index f558e87..2b7f86f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
 import androidx.test.filters.SmallTest
+import com.android.internal.policy.SystemBarUtils
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -67,8 +68,7 @@
 
         createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
-        val transitionHeight = context.resources.getDimensionPixelSize(
-            R.dimen.desktop_mode_transition_region_thickness)
+        val transitionHeight = SystemBarUtils.getStatusBarHeight(context)
         val toFullscreenScale = mContext.resources.getFloat(
             R.dimen.desktop_mode_fullscreen_region_scale
         )
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index c0cedf1..fddcf29 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -25,14 +25,6 @@
 
 namespace text_feature {
 
-inline bool deprecate_ui_fonts() {
-#ifdef __ANDROID__
-    return com_android_text_flags_deprecate_ui_fonts();
-#else
-    return true;
-#endif  // __ANDROID__
-}
-
 inline bool letter_spacing_justification() {
 #ifdef __ANDROID__
     return com_android_text_flags_letter_spacing_justification();
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index d66d7f8..ede385a 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -53,9 +53,7 @@
     if (familyVariant.has_value()) {
         minikinPaint.familyVariant = familyVariant.value();
     } else {
-        minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
-                                             ? minikin::FamilyVariant::ELEGANT
-                                             : minikin::FamilyVariant::DEFAULT;
+        minikinPaint.familyVariant = minikin::FamilyVariant::ELEGANT;
     }
     return minikinPaint;
 }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index dfda25d..8ec0430 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -654,6 +654,9 @@
         if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
             const auto inputEventId =
                     static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
+            ATRACE_FORMAT(
+                "frameTimelineInfo(frameNumber=%llu, vsyncId=%lld, inputEventId=0x%" PRIx32 ")",
+                frameCompleteNr, vsyncId, inputEventId);
             const ANativeWindowFrameTimelineInfo ftl = {
                     .frameNumber = frameCompleteNr,
                     .frameTimelineVsyncId = vsyncId,
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index ea5a036..b28237c 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -69,6 +69,29 @@
     private static final String TAG = "ApduServiceInfo";
 
     /**
+     * Component level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for a system application to change its icon and label
+     * on the default applications page. This property should be added to an
+     * {@link HostApduService} declaration in the manifest.
+     *
+     * <p>For example:
+     * <pre>
+     * &lt;service
+     *     android:apduServiceBanner="@drawable/product_logo"
+     *     android:label="@string/service_label"&gt
+     *      &lt;property
+     *          android:name="android.content.PROPERTY_WALLET_ICON_AND_LABEL_HOLDER"
+     *          android:value="true"/&gt;
+     * &lt/service&gt;
+     * </pre>
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ICON_PROPERTY_ENABLED)
+    public static final String PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL =
+            "android.nfc.cardemulation.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL";
+
+    /**
      * The service that implements this
      */
     private final ResolveInfo mService;
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 3409c29..defbc11 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,8 +16,6 @@
 
 package com.android.externalstorage;
 
-import static java.util.regex.Pattern.CASE_INSENSITIVE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
@@ -61,12 +59,15 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
-import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Presents content of the shared (a.k.a. "external") storage.
@@ -89,12 +90,9 @@
     private static final Uri BASE_URI =
             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
-    /**
-     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
-     * {@code /Android/sandbox/} along with all their subdirectories and content.
-     */
-    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
-            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
+    private static final String PRIMARY_EMULATED_STORAGE_PATH = "/storage/emulated/";
+
+    private static final String STORAGE_PATH = "/storage/";
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -309,11 +307,70 @@
             return false;
         }
 
-        final String path = getPathFromDocId(documentId);
-        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
+        try {
+            final RootInfo root = getRootFromDocId(documentId);
+            final String canonicalPath = getPathFromDocId(documentId);
+            return isRestrictedPath(root.rootId, canonicalPath);
+        } catch (Exception e) {
+            return true;
+        }
     }
 
     /**
+     * Based on the given root id and path, we restrict path access if file is Android/data or
+     * Android/obb or Android/sandbox or one of their subdirectories.
+     *
+     * @param canonicalPath of the file
+     * @return true if path is restricted
+     */
+    private boolean isRestrictedPath(String rootId, String canonicalPath) {
+        if (rootId == null || canonicalPath == null) {
+            return true;
+        }
+
+        final String rootPath;
+        if (rootId.equalsIgnoreCase(ROOT_ID_PRIMARY_EMULATED)) {
+            // Creates "/storage/emulated/<user-id>"
+            rootPath = PRIMARY_EMULATED_STORAGE_PATH + UserHandle.myUserId();
+        } else {
+            // Creates "/storage/<volume-uuid>"
+            rootPath = STORAGE_PATH + rootId;
+        }
+        List<java.nio.file.Path> restrictedPathList = Arrays.asList(
+                Paths.get(rootPath, "Android", "data"),
+                Paths.get(rootPath, "Android", "obb"),
+                Paths.get(rootPath, "Android", "sandbox"));
+        // We need to identify restricted parent paths which actually exist on the device
+        List<java.nio.file.Path> validRestrictedPathsToCheck = restrictedPathList.stream().filter(
+                Files::exists).collect(Collectors.toList());
+
+        boolean isRestricted = false;
+        java.nio.file.Path filePathToCheck = Paths.get(rootPath, canonicalPath);
+        try {
+            while (filePathToCheck != null) {
+                for (java.nio.file.Path restrictedPath : validRestrictedPathsToCheck) {
+                    if (Files.isSameFile(restrictedPath, filePathToCheck)) {
+                        isRestricted = true;
+                        Log.v(TAG, "Restricting access for path: " + filePathToCheck);
+                        break;
+                    }
+                }
+                if (isRestricted) {
+                    break;
+                }
+
+                filePathToCheck = filePathToCheck.getParent();
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Error in checking file equality check.", e);
+            isRestricted = true;
+        }
+
+        return isRestricted;
+    }
+
+
+    /**
      * Check that the directory is the root of storage or blocked file from tree.
      * <p>
      * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 0cb85d8..b997c35 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -42,6 +42,8 @@
         "SettingsLibIllustrationPreference",
         "SettingsLibLayoutPreference",
         "SettingsLibMainSwitchPreference",
+        "SettingsLibMetadata",
+        "SettingsLibPreference",
         "SettingsLibProfileSelector",
         "SettingsLibProgressBar",
         "SettingsLibRestrictedLockUtils",
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index 9665dbd..17852e8 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -18,6 +18,7 @@
         "SettingsLibMetadata",
         "androidx.annotation_annotation",
         "androidx.preference_preference",
+        "guava",
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index 2072009..68f640b 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -79,7 +79,7 @@
      * This is for flagging purpose. If false (e.g. flag is disabled), xml resource is used to build
      * preference screen.
      */
-    protected open fun usePreferenceScreenMetadata(): Boolean = true
+    protected open fun usePreferenceScreenMetadata(): Boolean = false
 
     /** Returns the xml resource to create preference screen. */
     @XmlRes protected open fun getPreferenceScreenResId(context: Context): Int = 0
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index bd1e5a5..7994924 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -31,13 +31,14 @@
 import android.view.MenuItem;
 
 import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settingslib.preference.PreferenceFragment;
+
 /**
- * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events.
+ * Preference fragment that has hooks to observe fragment lifecycle events.
  */
-public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
+public abstract class ObservablePreferenceFragment extends PreferenceFragment
         implements LifecycleOwner {
 
     private final Lifecycle mLifecycle = new Lifecycle(this);
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3e2d832..d3c345d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -98,7 +98,7 @@
      */
     suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
 
-    suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
+    suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode)
 
     /** Gets audio device category. */
     @AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
@@ -248,8 +248,8 @@
         }
     }
 
-    override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
-        withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
+    override suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode) {
+        withContext(backgroundCoroutineContext) { audioManager.ringerModeInternal = mode.value }
     }
 
     @AudioDeviceCategory
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 08863b5..dca890d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -68,7 +68,7 @@
         if (audioStream.value == AudioManager.STREAM_RING) {
             val mode =
                 if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
-            audioRepository.setRingerMode(audioStream, RingerMode(mode))
+            audioRepository.setRingerModeInternal(audioStream, RingerMode(mode))
         }
         val mutedChanged = audioRepository.setMuted(audioStream, isMuted)
         if (mutedChanged && !isMuted) {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 52e6391..8a3b1df 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -74,6 +74,8 @@
 
     private lateinit var underTest: AudioRepository
 
+    private var ringerModeInternal: RingerMode = RingerMode(AudioManager.RINGER_MODE_NORMAL)
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -82,7 +84,7 @@
         `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
         `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
         `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
-        `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        `when`(audioManager.ringerModeInternal).then { ringerModeInternal.value }
         `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
             val streamType = it.arguments[0] as Int
             volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
@@ -103,6 +105,10 @@
         `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
             isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
         }
+        `when`(audioManager.setRingerModeInternal(anyInt())).then {
+            ringerModeInternal = RingerMode(it.arguments[0] as Int)
+            Unit
+        }
 
         underTest =
             AudioRepositoryImpl(
@@ -137,7 +143,7 @@
             underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
             runCurrent()
 
-            `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+            ringerModeInternal = RingerMode(AudioManager.RINGER_MODE_SILENT)
             triggerEvent(AudioManagerEvent.InternalRingerModeChanged)
             runCurrent()
 
@@ -150,6 +156,19 @@
     }
 
     @Test
+    fun changingRingerMode_changesRingerModeInternal() {
+        testScope.runTest {
+            underTest.setRingerModeInternal(
+                AudioStream(AudioManager.STREAM_SYSTEM),
+                RingerMode(AudioManager.RINGER_MODE_SILENT),
+            )
+            runCurrent()
+
+            assertThat(ringerModeInternal).isEqualTo(RingerMode(AudioManager.RINGER_MODE_SILENT))
+        }
+    }
+
+    @Test
     fun communicationDeviceChanges_repositoryEmits() {
         testScope.runTest {
             var device: AudioDeviceInfo? = null
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7c89592..ad14035 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -616,16 +616,6 @@
 }
 
 flag {
-    name: "screenshot_save_image_exporter"
-    namespace: "systemui"
-    description: "Save all screenshots using ImageExporter"
-    bug: "352308052"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "screenshot_ui_controller_refactor"
     namespace: "systemui"
     description: "Simplify and refactor ScreenshotController"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index ba885f7..1f98cd8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -65,7 +65,6 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformOutlinedButton
 import com.android.compose.animation.Easings
@@ -355,7 +354,10 @@
     fun Content(modifier: Modifier) {
 
         // Wrap PIN entry in a Box so it is visible to accessibility (even if empty).
-        Box(modifier = modifier.fillMaxWidth().wrapContentHeight()) {
+        Box(
+            modifier = modifier.fillMaxWidth().wrapContentHeight(),
+            contentAlignment = Alignment.Center,
+        ) {
             Row(
                 modifier
                     .heightIn(min = shapeAnimations.shapeSize)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
index e1f73e3..4e8121f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
@@ -17,9 +17,12 @@
 
 package com.android.systemui.common.ui.compose
 
+import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
 
 /** Returns the loaded [String] or `null` if there isn't one. */
 @Composable
@@ -29,3 +32,7 @@
         is Text.Resource -> stringResource(res)
     }
 }
+
+fun Text.toAnnotatedString(context: Context): AnnotatedString? {
+    return loadText(context)?.let { AnnotatedString(it) }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 0d05f4e..fb9dde3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -200,7 +200,7 @@
     override fun onPointerEvent(
         pointerEvent: PointerEvent,
         pass: PointerEventPass,
-        bounds: IntSize
+        bounds: IntSize,
     ) {
         // The order is important here: the tracker is always called first.
         pointerTracker.onPointerEvent(pointerEvent, pass, bounds)
@@ -234,8 +234,8 @@
                     pointersDown == 0 -> {
                         startedPosition = null
 
-                        // This is the last pointer up
-                        velocityTracker.addPointerInputChange(changes.single())
+                        val lastPointerUp = changes.single { it.id == velocityPointerId }
+                        velocityTracker.addPointerInputChange(lastPointerUp)
                     }
 
                     // The first pointer down, startedPosition was not set.
@@ -271,7 +271,12 @@
 
                         // If the previous pointer has been removed, we use the first available
                         // change to keep tracking the velocity.
-                        velocityPointerId = pointerChange.id
+                        velocityPointerId =
+                            if (pointerChange.pressed) {
+                                pointerChange.id
+                            } else {
+                                changes.first { it.pressed }.id
+                            }
 
                         velocityTracker.addPointerInputChange(pointerChange)
                     }
@@ -312,13 +317,13 @@
                                             velocityTracker.calculateVelocity(maxVelocity)
                                         }
                                         .toFloat(),
-                                onFling = { controller.onStop(it, canChangeContent = true) }
+                                onFling = { controller.onStop(it, canChangeContent = true) },
                             )
                         },
                         onDragCancel = { controller ->
                             startFlingGesture(
                                 initialVelocity = 0f,
-                                onFling = { controller.onStop(it, canChangeContent = true) }
+                                onFling = { controller.onStop(it, canChangeContent = true) },
                             )
                         },
                         swipeDetector = swipeDetector,
@@ -369,10 +374,7 @@
         // PreScroll phase
         val consumedByPreScroll =
             dispatcher
-                .dispatchPreScroll(
-                    available = availableOnPreScroll.toOffset(),
-                    source = source,
-                )
+                .dispatchPreScroll(available = availableOnPreScroll.toOffset(), source = source)
                 .toFloat()
 
         // Scroll phase
@@ -484,12 +486,12 @@
                         Orientation.Horizontal ->
                             awaitHorizontalTouchSlopOrCancellation(
                                 consumablePointer.id,
-                                onSlopReached
+                                onSlopReached,
                             )
                         Orientation.Vertical ->
                             awaitVerticalTouchSlopOrCancellation(
                                 consumablePointer.id,
-                                onSlopReached
+                                onSlopReached,
                             )
                     }
 
@@ -553,7 +555,7 @@
     }
 
     private suspend fun AwaitPointerEventScope.awaitConsumableEvent(
-        pass: () -> PointerEventPass,
+        pass: () -> PointerEventPass
     ): PointerEvent {
         fun canBeConsumed(changes: List<PointerInputChange>): Boolean {
             // At least one pointer down AND
@@ -661,7 +663,4 @@
     fun pointersInfo(): PointersInfo
 }
 
-internal data class PointersInfo(
-    val startedPosition: Offset?,
-    val pointersDown: Int,
-)
+internal data class PointersInfo(val startedPosition: Offset?, val pointersDown: Int)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index bf192e7..af717ac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -259,7 +259,7 @@
                                         it
                                     }
                                 ),
-                                Orientation.Vertical
+                                Orientation.Vertical,
                             )
                             .fillMaxSize()
                     )
@@ -438,6 +438,9 @@
 
         continueDraggingDown()
         assertThat(stopped).isTrue()
+
+        // Complete the gesture
+        rule.onRoot().performTouchInput { up() }
     }
 
     @Test
@@ -640,7 +643,7 @@
                 override fun onPostScroll(
                     consumed: Offset,
                     available: Offset,
-                    source: NestedScrollSource
+                    source: NestedScrollSource,
                 ): Offset {
                     availableOnPostScroll = available.y
                     return Offset.Zero
@@ -653,7 +656,7 @@
 
                 override suspend fun onPostFling(
                     consumed: Velocity,
-                    available: Velocity
+                    available: Velocity,
                 ): Velocity {
                     availableOnPostFling = available.y
                     return Velocity.Zero
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 75a77cf..4bc71fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -27,6 +27,17 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 
 /** Create [FingerprintSensorPropertiesInternal] for a test. */
 internal fun fingerprintSensorPropertiesInternal(
@@ -145,3 +156,67 @@
     info.negativeButtonText = negativeButton
     return info
 }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal fun TestScope.updateSfpsIndicatorRequests(
+    kosmos: Kosmos,
+    mContext: SysuiTestableContext,
+    primaryBouncerRequest: Boolean? = null,
+    alternateBouncerRequest: Boolean? = null,
+    biometricPromptRequest: Boolean? = null,
+    // TODO(b/365182034): update when rest to unlock feature is implemented
+    //    progressBarShowing: Boolean? = null
+) {
+    biometricPromptRequest?.let { hasBiometricPromptRequest ->
+        if (hasBiometricPromptRequest) {
+            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+        } else {
+            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+        }
+    }
+
+    primaryBouncerRequest?.let { hasPrimaryBouncerRequest ->
+        updatePrimaryBouncer(
+            kosmos,
+            mContext,
+            isShowing = hasPrimaryBouncerRequest,
+            isAnimatingAway = false,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true
+        )
+    }
+
+    alternateBouncerRequest?.let { hasAlternateBouncerRequest ->
+        kosmos.keyguardBouncerRepository.setAlternateVisible(hasAlternateBouncerRequest)
+    }
+
+    // TODO(b/365182034): set progress bar visibility when rest to unlock feature is implemented
+
+    runCurrent()
+}
+
+internal fun updatePrimaryBouncer(
+    kosmos: Kosmos,
+    mContext: SysuiTestableContext,
+    isShowing: Boolean,
+    isAnimatingAway: Boolean,
+    fpsDetectionRunning: Boolean,
+    isUnlockingWithFpAllowed: Boolean,
+) {
+    kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
+    kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
+    val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
+    kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
+        primaryStartDisappearAnimation
+    )
+
+    whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
+        .thenReturn(fpsDetectionRunning)
+    whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+        .thenReturn(isUnlockingWithFpAllowed)
+    mContext.orCreateTestableResources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, true)
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
new file mode 100644
index 0000000..298b54a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.domain.interactor
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.updateSfpsIndicatorRequests
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayStateRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SideFpsOverlayInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val underTest = kosmos.sideFpsOverlayInteractor
+
+    @Test
+    fun verifyIsShowingFalse_whenInRearDisplayMode() {
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+            setupTestConfiguration(isInRearDisplayMode = true)
+
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
+            runCurrent()
+
+            assertThat(isShowing).isFalse()
+        }
+    }
+
+    @Test
+    fun verifyIsShowingUpdates_onPrimaryBouncerShowAndHide() {
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+            setupTestConfiguration(isInRearDisplayMode = false)
+
+            // Show primary bouncer
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
+            runCurrent()
+
+            assertThat(isShowing).isTrue()
+
+            // Hide primary bouncer
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = false)
+            runCurrent()
+
+            assertThat(isShowing).isFalse()
+        }
+    }
+
+    @Test
+    fun verifyIsShowingUpdates_onAlternateBouncerShowAndHide() {
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+            setupTestConfiguration(isInRearDisplayMode = false)
+
+            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = true)
+            runCurrent()
+
+            assertThat(isShowing).isTrue()
+
+            // Hide alternate bouncer
+            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = false)
+            runCurrent()
+
+            assertThat(isShowing).isFalse()
+        }
+    }
+
+    @Test
+    fun verifyIsShowingUpdates_onSystemServerAuthenticationStartedAndStopped() {
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+            setupTestConfiguration(isInRearDisplayMode = false)
+
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = true)
+            runCurrent()
+
+            assertThat(isShowing).isTrue()
+
+            // System server authentication stopped
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = false)
+            runCurrent()
+
+            assertThat(isShowing).isFalse()
+        }
+    }
+
+    // On progress bar shown - hide indicator
+    // On progress bar hidden - show indicator
+    // TODO(b/365182034): update + enable when rest to unlock feature is implemented
+    @Ignore("b/365182034")
+    @Test
+    fun verifyIsShowingUpdates_onProgressBarInteraction() {
+        kosmos.testScope.runTest {
+            val isShowing by collectLastValue(underTest.isShowing)
+            setupTestConfiguration(isInRearDisplayMode = false)
+
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
+            runCurrent()
+
+            assertThat(isShowing).isTrue()
+
+            //            updateSfpsIndicatorRequests(
+            //                kosmos, mContext, primaryBouncerRequest = true, progressBarShowing =
+            // true
+            //            )
+            runCurrent()
+
+            assertThat(isShowing).isFalse()
+
+            // Set progress bar invisible
+            //            updateSfpsIndicatorRequests(
+            //                kosmos, mContext, primaryBouncerRequest = true, progressBarShowing =
+            // false
+            //            )
+            runCurrent()
+
+            // Verify indicator shown
+            assertThat(isShowing).isTrue()
+        }
+    }
+
+    private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
+        kosmos.fingerprintPropertyRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.POWER_BUTTON,
+            sensorLocations = emptyMap()
+        )
+
+        kosmos.displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode)
+        kosmos.displayRepository.emitDisplayChangeEvent(0)
+        runCurrent()
+
+        kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+            AuthenticationReason.NotRunning
+        )
+        // TODO(b/365182034): set progress bar visibility once rest to unlock feature is implemented
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 7fa165c..2eea668 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -16,64 +16,48 @@
 
 package com.android.systemui.biometrics.ui.binder
 
-import android.animation.Animator
-import android.graphics.Rect
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManagerGlobal
 import android.testing.TestableLooper
-import android.view.Display
-import android.view.DisplayInfo
 import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
 import android.view.WindowManager
-import android.view.WindowMetrics
 import android.view.layoutInflater
 import android.view.windowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
-import com.android.keyguard.keyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
-import com.android.systemui.biometrics.data.repository.biometricStatusRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.biometrics.updateSfpsIndicatorRequests
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.display.data.repository.displayStateRepository
-import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.firstValue
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -83,84 +67,25 @@
     private val kosmos = testKosmos()
 
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
-    @Mock private lateinit var displayManager: DisplayManager
-    @Mock
-    private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
     @Mock private lateinit var layoutInflater: LayoutInflater
     @Mock private lateinit var sideFpsView: View
-
-    private val contextDisplayInfo = DisplayInfo()
-
-    private var displayWidth: Int = 0
-    private var displayHeight: Int = 0
-    private var boundsWidth: Int = 0
-    private var boundsHeight: Int = 0
-
-    private lateinit var deviceConfig: DeviceConfig
-    private lateinit var sensorLocation: SensorLocationInternal
-
-    enum class DeviceConfig {
-        X_ALIGNED,
-        Y_ALIGNED,
-    }
+    @Captor private lateinit var viewCaptor: ArgumentCaptor<View>
 
     @Before
     fun setup() {
         allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
-
-        mContext = spy(mContext)
-
-        val resources = mContext.resources
-        whenever(mContext.display)
-            .thenReturn(
-                Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
-            )
-
         kosmos.layoutInflater = layoutInflater
-
-        whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
-            .thenReturn(MutableStateFlow(false))
-
-        context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, kosmos.windowManager)
-
         `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
         `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
             .thenReturn(mock(LottieAnimationView::class.java))
-        with(mock(ViewPropertyAnimator::class.java)) {
-            `when`(sideFpsView.animate()).thenReturn(this)
-            `when`(alpha(Mockito.anyFloat())).thenReturn(this)
-            `when`(setStartDelay(Mockito.anyLong())).thenReturn(this)
-            `when`(setDuration(Mockito.anyLong())).thenReturn(this)
-            `when`(setListener(any())).thenAnswer {
-                (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
-                    mock(Animator::class.java)
-                )
-                this
-            }
-        }
     }
 
     @Test
     fun verifyIndicatorNotAdded_whenInRearDisplayMode() {
         kosmos.testScope.runTest {
-            setupTestConfiguration(
-                DeviceConfig.X_ALIGNED,
-                rotation = DisplayRotation.ROTATION_0,
-                isInRearDisplayMode = true
-            )
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-            updatePrimaryBouncer(
-                isShowing = true,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
-            runCurrent()
-
+            setupTestConfiguration(isInRearDisplayMode = true)
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
             verify(kosmos.windowManager, never()).addView(any(), any())
         }
     }
@@ -168,33 +93,14 @@
     @Test
     fun verifyIndicatorShowAndHide_onPrimaryBouncerShowAndHide() {
         kosmos.testScope.runTest {
-            setupTestConfiguration(
-                DeviceConfig.X_ALIGNED,
-                rotation = DisplayRotation.ROTATION_0,
-                isInRearDisplayMode = false
-            )
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-            // Show primary bouncer
-            updatePrimaryBouncer(
-                isShowing = true,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            setupTestConfiguration(isInRearDisplayMode = false)
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
             runCurrent()
 
             verify(kosmos.windowManager).addView(any(), any())
 
             // Hide primary bouncer
-            updatePrimaryBouncer(
-                isShowing = false,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = false)
             runCurrent()
 
             verify(kosmos.windowManager).removeView(any())
@@ -204,30 +110,19 @@
     @Test
     fun verifyIndicatorShowAndHide_onAlternateBouncerShowAndHide() {
         kosmos.testScope.runTest {
-            setupTestConfiguration(
-                DeviceConfig.X_ALIGNED,
-                rotation = DisplayRotation.ROTATION_0,
-                isInRearDisplayMode = false
-            )
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-            // Show alternate bouncer
-            kosmos.keyguardBouncerRepository.setAlternateVisible(true)
+            setupTestConfiguration(isInRearDisplayMode = false)
+            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = true)
             runCurrent()
 
             verify(kosmos.windowManager).addView(any(), any())
 
-            var viewCaptor = argumentCaptor<View>()
             verify(kosmos.windowManager).addView(viewCaptor.capture(), any())
             verify(viewCaptor.firstValue)
                 .announceForAccessibility(
                     mContext.getText(R.string.accessibility_side_fingerprint_indicator_label)
                 )
 
-            // Hide alternate bouncer
-            kosmos.keyguardBouncerRepository.setAlternateVisible(false)
+            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = false)
             runCurrent()
 
             verify(kosmos.windowManager).removeView(any())
@@ -237,30 +132,14 @@
     @Test
     fun verifyIndicatorShownAndHidden_onSystemServerAuthenticationStartedAndStopped() {
         kosmos.testScope.runTest {
-            setupTestConfiguration(
-                DeviceConfig.X_ALIGNED,
-                rotation = DisplayRotation.ROTATION_0,
-                isInRearDisplayMode = false
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-            updatePrimaryBouncer(
-                isShowing = false,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
-            // System server authentication started
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.BiometricPromptAuthentication
-            )
+            setupTestConfiguration(isInRearDisplayMode = false)
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = true)
             runCurrent()
 
             verify(kosmos.windowManager).addView(any(), any())
 
             // System server authentication stopped
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = false)
             runCurrent()
 
             verify(kosmos.windowManager).removeView(any())
@@ -269,45 +148,35 @@
 
     // On progress bar shown - hide indicator
     // On progress bar hidden - show indicator
+    // TODO(b/365182034): update + enable when rest to unlock feature is implemented
+    @Ignore("b/365182034")
     @Test
     fun verifyIndicatorProgressBarInteraction() {
         kosmos.testScope.runTest {
             // Pre-auth conditions
-            setupTestConfiguration(
-                DeviceConfig.X_ALIGNED,
-                rotation = DisplayRotation.ROTATION_0,
-                isInRearDisplayMode = false
-            )
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-
-            // Show primary bouncer
-            updatePrimaryBouncer(
-                isShowing = true,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            setupTestConfiguration(isInRearDisplayMode = false)
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
             runCurrent()
 
             val inOrder = inOrder(kosmos.windowManager)
-
             // Verify indicator shown
             inOrder.verify(kosmos.windowManager).addView(any(), any())
 
             // Set progress bar visible
-            kosmos.sideFpsProgressBarViewModel.setVisible(true)
-
+            //            updateSfpsIndicatorRequests(
+            //                kosmos, mContext, primaryBouncerRequest = true, progressBarShowing =
+            // true
+            //            )
             runCurrent()
 
             // Verify indicator hidden
             inOrder.verify(kosmos.windowManager).removeView(any())
 
             // Set progress bar invisible
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-
+            //            updateSfpsIndicatorRequests(
+            //                kosmos, mContext, primaryBouncerRequest = true, progressBarShowing =
+            // false
+            //            )
             runCurrent()
 
             // Verify indicator shown
@@ -315,78 +184,18 @@
         }
     }
 
-    private fun updatePrimaryBouncer(
-        isShowing: Boolean,
-        isAnimatingAway: Boolean,
-        fpsDetectionRunning: Boolean,
-        isUnlockingWithFpAllowed: Boolean,
-    ) {
-        kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
-        kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
-        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
-        kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
-            primaryStartDisappearAnimation
-        )
-
-        whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
-            .thenReturn(fpsDetectionRunning)
-        whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
-            .thenReturn(isUnlockingWithFpAllowed)
-        mContext.orCreateTestableResources.addOverride(
-            R.bool.config_show_sidefps_hint_on_bouncer,
-            true
-        )
-    }
-
-    private suspend fun TestScope.setupTestConfiguration(
-        deviceConfig: DeviceConfig,
-        rotation: DisplayRotation = DisplayRotation.ROTATION_0,
-        isInRearDisplayMode: Boolean,
-    ) {
-        this@SideFpsOverlayViewBinderTest.deviceConfig = deviceConfig
-
-        when (deviceConfig) {
-            DeviceConfig.X_ALIGNED -> {
-                displayWidth = 3000
-                displayHeight = 1500
-                boundsWidth = 200
-                boundsHeight = 100
-                sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2)
-            }
-            DeviceConfig.Y_ALIGNED -> {
-                displayWidth = 2500
-                displayHeight = 2000
-                boundsWidth = 100
-                boundsHeight = 200
-                sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2)
-            }
-        }
-
-        whenever(kosmos.windowManager.maximumWindowMetrics)
-            .thenReturn(
-                WindowMetrics(
-                    Rect(0, 0, displayWidth, displayHeight),
-                    mock(WindowInsets::class.java),
-                )
-            )
-
-        contextDisplayInfo.uniqueId = DISPLAY_ID
-
+    private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
         kosmos.fingerprintPropertyRepository.setProperties(
             sensorId = 1,
             strength = SensorStrength.STRONG,
             sensorType = FingerprintSensorType.POWER_BUTTON,
-            sensorLocations = mapOf(DISPLAY_ID to sensorLocation)
+            sensorLocations = emptyMap()
         )
 
         kosmos.displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode)
-        kosmos.displayStateRepository.setCurrentRotation(rotation)
+        kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
         kosmos.displayRepository.emitDisplayChangeEvent(0)
         kosmos.sideFpsOverlayViewBinder.start()
         runCurrent()
     }
-
-    companion object {
-        private const val DISPLAY_ID = "displayId"
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 0db7b62..27b1371 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -30,23 +30,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.model.KeyPath
-import com.android.keyguard.keyguardUpdateMonitor
 import com.android.settingslib.Utils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
-import com.android.systemui.biometrics.data.repository.biometricStatusRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
-import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.LottieCallback
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.biometrics.updateSfpsIndicatorRequests
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.display.data.repository.displayStateRepository
-import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
@@ -284,17 +280,7 @@
         kosmos.testScope.runTest {
             val lottieCallbacks by collectLastValue(kosmos.sideFpsOverlayViewModel.lottieCallbacks)
 
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.NotRunning
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-
-            updatePrimaryBouncer(
-                isShowing = true,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
             runCurrent()
 
             assertThat(lottieCallbacks)
@@ -312,17 +298,7 @@
             val lottieCallbacks by collectLastValue(kosmos.sideFpsOverlayViewModel.lottieCallbacks)
             setDarkMode(true)
 
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.BiometricPromptAuthentication
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-
-            updatePrimaryBouncer(
-                isShowing = false,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = true)
             runCurrent()
 
             assertThat(lottieCallbacks)
@@ -338,17 +314,7 @@
             val lottieCallbacks by collectLastValue(kosmos.sideFpsOverlayViewModel.lottieCallbacks)
             setDarkMode(false)
 
-            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
-                AuthenticationReason.BiometricPromptAuthentication
-            )
-            kosmos.sideFpsProgressBarViewModel.setVisible(false)
-
-            updatePrimaryBouncer(
-                isShowing = false,
-                isAnimatingAway = false,
-                fpsDetectionRunning = true,
-                isUnlockingWithFpAllowed = true
-            )
+            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = true)
             runCurrent()
 
             assertThat(lottieCallbacks)
@@ -371,29 +337,6 @@
         mContext.resources.configuration.uiMode = uiMode
     }
 
-    private fun updatePrimaryBouncer(
-        isShowing: Boolean,
-        isAnimatingAway: Boolean,
-        fpsDetectionRunning: Boolean,
-        isUnlockingWithFpAllowed: Boolean,
-    ) {
-        kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
-        kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
-        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
-        kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
-            primaryStartDisappearAnimation
-        )
-
-        whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
-            .thenReturn(fpsDetectionRunning)
-        whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
-            .thenReturn(isUnlockingWithFpAllowed)
-        mContext.orCreateTestableResources.addOverride(
-            R.bool.config_show_sidefps_hint_on_bouncer,
-            true
-        )
-    }
-
     private suspend fun TestScope.setupTestConfiguration(
         deviceConfig: DeviceConfig,
         rotation: DisplayRotation = DisplayRotation.ROTATION_0,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
index cd0c58f..c4fc132 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -19,12 +19,15 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.education.data.repository.contextualEducationRepository
 import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.keyboard.data.repository.keyboardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.touchpadRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -36,22 +39,62 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
+    private val keyboardRepository = kosmos.keyboardRepository
+    private val touchpadRepository = kosmos.touchpadRepository
+    private val repository = kosmos.contextualEducationRepository
 
     @Test
-    fun dataUpdatedOnIncrementSignalCount() =
+    fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
         testScope.runTest {
-            val model by
-                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+            touchpadRepository.setIsAnyTouchpadConnected(true)
+
+            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
             val originalValue = model!!.signalCount
             underTest.incrementSignalCount(BACK)
+
             assertThat(model?.signalCount).isEqualTo(originalValue + 1)
         }
 
     @Test
+    fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
+        testScope.runTest {
+            touchpadRepository.setIsAnyTouchpadConnected(false)
+
+            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+            val originalValue = model!!.signalCount
+            underTest.incrementSignalCount(BACK)
+
+            assertThat(model?.signalCount).isEqualTo(originalValue)
+        }
+
+    @Test
+    fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
+        testScope.runTest {
+            keyboardRepository.setIsAnyKeyboardConnected(true)
+
+            val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+            val originalValue = model!!.signalCount
+            underTest.incrementSignalCount(ALL_APPS)
+
+            assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+        }
+
+    @Test
+    fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
+        testScope.runTest {
+            keyboardRepository.setIsAnyKeyboardConnected(false)
+
+            val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+            val originalValue = model!!.signalCount
+            underTest.incrementSignalCount(ALL_APPS)
+
+            assertThat(model?.signalCount).isEqualTo(originalValue)
+        }
+
+    @Test
     fun dataAddedOnUpdateShortcutTriggerTime() =
         testScope.runTest {
-            val model by
-                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
             assertThat(model?.lastShortcutTriggeredTime).isNull()
             underTest.updateShortcutTriggerTime(BACK)
             assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt
index 1e5599b..93ba265 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt
@@ -19,7 +19,6 @@
 import android.content.ComponentName
 import android.content.packageManager
 import android.content.pm.PackageManager
-import android.content.pm.ServiceInfo
 import android.content.pm.UserInfo
 import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,6 +34,7 @@
 import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
@@ -100,6 +100,7 @@
                         Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
                         Text.Loaded(tileService1),
                         Text.Loaded(appName1),
+                        TileCategory.PROVIDED_BY_APP,
                     )
                 val expectedData2 =
                     EditTileData(
@@ -107,6 +108,7 @@
                         Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)),
                         Text.Loaded(tileService2),
                         Text.Loaded(appName2),
+                        TileCategory.PROVIDED_BY_APP,
                     )
 
                 assertThat(editTileDataList).containsExactly(expectedData1, expectedData2)
@@ -144,6 +146,7 @@
                         Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
                         Text.Loaded(tileService1),
                         Text.Loaded(appName1),
+                        TileCategory.PROVIDED_BY_APP,
                     )
 
                 val editTileDataList = underTest.getCustomTileData()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt
index deefbf5..053a59a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
 import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
 import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
@@ -132,6 +133,7 @@
                         icon = Icon.Loaded(icon, ContentDescription.Loaded(tileName)),
                         label = Text.Loaded(tileName),
                         appName = Text.Loaded(appName),
+                        category = TileCategory.PROVIDED_BY_APP,
                     )
 
                 assertThat(editTiles.customTiles).hasSize(1)
@@ -181,7 +183,8 @@
                 tileSpec = this,
                 icon = Icon.Resource(android.R.drawable.star_on, ContentDescription.Loaded(spec)),
                 label = Text.Loaded(spec),
-                appName = null
+                appName = null,
+                category = TileCategory.UNKNOWN,
             )
         }
 
@@ -192,6 +195,7 @@
                     Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)),
                 label = Text.Resource(uiConfig.labelRes),
                 appName = null,
+                category = category,
             )
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 7f01fad..484a8ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
+import androidx.compose.ui.text.AnnotatedString
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.model.GridCell
@@ -28,6 +28,7 @@
 import com.android.systemui.qs.panels.ui.model.TileGridCell
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -141,10 +142,11 @@
                 EditTileViewModel(
                     tileSpec = TileSpec.create(tileSpec),
                     icon = Icon.Resource(0, null),
-                    label = Text.Loaded("unused"),
+                    label = AnnotatedString("unused"),
                     appName = null,
                     isCurrent = true,
                     availableEditActions = emptySet(),
+                    category = TileCategory.UNKNOWN,
                 ),
                 width,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
index 601779f..583db72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.ui.compose.toAnnotatedString
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
@@ -42,6 +43,7 @@
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
 import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
 import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
@@ -190,7 +192,7 @@
                     .forEach {
                         val data = getEditTileData(it.tileSpec)
 
-                        assertThat(it.label).isEqualTo(data.label)
+                        assertThat(it.label).isEqualTo(data.label.toAnnotatedString(context))
                         assertThat(it.icon).isEqualTo(data.icon)
                         assertThat(it.appName).isNull()
                     }
@@ -224,15 +226,19 @@
 
                 // service1
                 val model1 = tiles!!.first { it.tileSpec == TileSpec.create(component1) }
-                assertThat(model1.label).isEqualTo(Text.Loaded(tileService1))
-                assertThat(model1.appName).isEqualTo(Text.Loaded(appName1))
+                assertThat(model1.label)
+                    .isEqualTo(Text.Loaded(tileService1).toAnnotatedString(context))
+                assertThat(model1.appName)
+                    .isEqualTo(Text.Loaded(appName1).toAnnotatedString(context))
                 assertThat(model1.icon)
                     .isEqualTo(Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)))
 
                 // service2
                 val model2 = tiles!!.first { it.tileSpec == TileSpec.create(component2) }
-                assertThat(model2.label).isEqualTo(Text.Loaded(tileService2))
-                assertThat(model2.appName).isEqualTo(Text.Loaded(appName2))
+                assertThat(model2.label)
+                    .isEqualTo(Text.Loaded(tileService2).toAnnotatedString(context))
+                assertThat(model2.appName)
+                    .isEqualTo(Text.Loaded(appName2).toAnnotatedString(context))
                 assertThat(model2.icon)
                     .isEqualTo(Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)))
             }
@@ -559,7 +565,8 @@
                 tileSpec = this,
                 icon = Icon.Resource(R.drawable.star_on, ContentDescription.Loaded(spec)),
                 label = Text.Loaded(spec),
-                appName = null
+                appName = null,
+                category = TileCategory.UNKNOWN,
             )
         }
 
@@ -570,6 +577,7 @@
                     Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)),
                 label = Text.Resource(uiConfig.labelRes),
                 appName = null,
+                category = category,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt
new file mode 100644
index 0000000..7f90e3b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.shared.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupAndSortCategoryAndNameTest : SysuiTestCase() {
+
+    private val elements =
+        listOf(
+            CategoryAndName(TileCategory.DISPLAY, "B"),
+            CategoryAndName(TileCategory.PRIVACY, "A"),
+            CategoryAndName(TileCategory.DISPLAY, "C"),
+            CategoryAndName(TileCategory.UTILITIES, "B"),
+            CategoryAndName(TileCategory.CONNECTIVITY, "A"),
+            CategoryAndName(TileCategory.PROVIDED_BY_APP, "B"),
+            CategoryAndName(TileCategory.CONNECTIVITY, "C"),
+            CategoryAndName(TileCategory.ACCESSIBILITY, "A")
+        )
+
+    @Test
+    fun allElementsInResult() {
+        val grouped = groupAndSort(elements)
+        val allValues = grouped.values.reduce { acc, el -> acc + el }
+        assertThat(allValues).containsExactlyElementsIn(elements)
+    }
+
+    @Test
+    fun groupedByCategory() {
+        val grouped = groupAndSort(elements)
+        grouped.forEach { tileCategory, categoryAndNames ->
+            categoryAndNames.forEach { element ->
+                assertThat(element.category).isEqualTo(tileCategory)
+            }
+        }
+    }
+
+    @Test
+    fun sortedAlphabeticallyInEachCategory() {
+        val grouped = groupAndSort(elements)
+        grouped.values.forEach { elements ->
+            assertThat(elements.map(CategoryAndName::name)).isInOrder()
+        }
+    }
+
+    @Test
+    fun categoriesSortedInNaturalOrder() {
+        val grouped = groupAndSort(elements)
+        assertThat(grouped.keys).isInOrder()
+    }
+
+    @Test
+    fun missingCategoriesAreNotInResult() {
+        val grouped = groupAndSort(elements.filterNot { it.category == TileCategory.CONNECTIVITY })
+        assertThat(grouped.keys).doesNotContain(TileCategory.CONNECTIVITY)
+    }
+
+    companion object {
+        private fun CategoryAndName(category: TileCategory, name: String): CategoryAndName {
+            return object : CategoryAndName {
+                override val category = category
+                override val name = name
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index aaad0fc..5a45060 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -184,7 +184,7 @@
                 )
 
             val networkModel =
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 4,
                     ssid = "test ssid",
                 )
@@ -219,7 +219,7 @@
                 )
 
             val networkModel =
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 4,
                     ssid = "test ssid",
                     hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
@@ -398,7 +398,7 @@
                 collectLastValue(
                     underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
                 )
-            val networkModel = WifiNetworkModel.Inactive
+            val networkModel = WifiNetworkModel.Inactive()
 
             connectivityRepository.setWifiConnected(validated = false)
             wifiRepository.setIsWifiDefault(true)
@@ -416,7 +416,7 @@
                     underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
                 )
 
-            val networkModel = WifiNetworkModel.Inactive
+            val networkModel = WifiNetworkModel.Inactive()
 
             connectivityRepository.setWifiConnected(validated = false)
             wifiRepository.setIsWifiDefault(true)
@@ -543,7 +543,7 @@
 
     private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
         val networkModel =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 level = 4,
                 ssid = "test ssid",
                 hotspotDeviceType = hotspot,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt
index 2444229..fa6d8bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
@@ -43,7 +44,8 @@
         QSTileConfig(
             TileSpec.create(RecordIssueModule.TILE_SPEC),
             uiConfig,
-            kosmos.qsEventLogger.getNewInstanceId()
+            kosmos.qsEventLogger.getNewInstanceId(),
+            TileCategory.UTILITIES,
         )
     private val resources = kosmos.mainResources
     private val theme = resources.newTheme()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeImageCapture.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeImageCapture.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/RecyclerViewActivity.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/RecyclerViewActivity.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/TestUserIds.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/scrim/ScrimViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scrim/ScrimViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ConstraintChangeTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ConstraintChangeTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ConstraintChangesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ConstraintChangesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 9481e5a..e0c4ab7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -431,7 +431,6 @@
                 mFakeKeyguardRepository,
                 mKeyguardTransitionInteractor,
                 mPowerInteractor,
-                mShadeRepository,
                 new FakeUserSetupRepository(),
                 mock(UserSwitcherInteractor.class),
                 new ShadeInteractorLegacyImpl(
@@ -447,8 +446,8 @@
                                 () -> mLargeScreenHeaderHelper
                         ),
                         mShadeRepository
-                )
-        );
+                ),
+                mKosmos.getShadeModeInteractor());
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(
                 mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 3f6617b..a52f173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -217,7 +217,6 @@
                 mKeyguardRepository,
                 keyguardTransitionInteractor,
                 powerInteractor,
-                mShadeRepository,
                 new FakeUserSetupRepository(),
                 mUserSwitcherInteractor,
                 new ShadeInteractorLegacyImpl(
@@ -232,8 +231,8 @@
                                 deviceEntryUdfpsInteractor,
                                 () -> mLargeScreenHeaderHelper),
                         mShadeRepository
-                )
-        );
+                ),
+                mKosmos.getShadeModeInteractor());
 
         mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
                         new ActiveNotificationListRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 9464c75..d163abf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -19,8 +19,6 @@
 import android.app.StatusBarManager.DISABLE2_NONE
 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -38,11 +36,7 @@
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -73,7 +67,6 @@
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     private val powerRepository by lazy { kosmos.fakePowerRepository }
-    private val shadeRepository by lazy { kosmos.fakeShadeRepository }
     private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     private val userRepository by lazy { kosmos.fakeUserRepository }
     private val userSetupRepository by lazy { kosmos.fakeUserSetupRepository }
@@ -142,9 +135,7 @@
             userSetupRepository.setUserSetUp(true)
 
             disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NOTIFICATION_SHADE,
-                )
+                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
 
             val actual by collectLastValue(underTest.isExpandToQsEnabled)
 
@@ -158,9 +149,7 @@
             userSetupRepository.setUserSetUp(true)
 
             disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_QUICK_SETTINGS,
-                )
+                DisableFlagsModel(disable2 = DISABLE2_QUICK_SETTINGS)
             val actual by collectLastValue(underTest.isExpandToQsEnabled)
 
             assertThat(actual).isFalse()
@@ -171,10 +160,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             userSetupRepository.setUserSetUp(true)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
 
             keyguardRepository.setIsDozing(true)
 
@@ -188,10 +174,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
 
             userSetupRepository.setUserSetUp(true)
 
@@ -205,10 +188,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
 
             userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = false))
 
@@ -222,10 +202,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
             userSetupRepository.setUserSetUp(true)
 
             val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -250,10 +227,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
             userSetupRepository.setUserSetUp(true)
 
             val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -262,17 +236,12 @@
 
             // WHEN QS is disabled
             disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_QUICK_SETTINGS,
-                )
+                DisableFlagsModel(disable2 = DISABLE2_QUICK_SETTINGS)
             // THEN expand is disabled
             assertThat(actual).isFalse()
 
             // WHEN QS is enabled
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
             // THEN expand is enabled
             assertThat(actual).isTrue()
         }
@@ -282,10 +251,7 @@
         testScope.runTest {
             deviceProvisioningRepository.setDeviceProvisioned(true)
             keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
             userSetupRepository.setUserSetUp(true)
 
             val actual by collectLastValue(underTest.isExpandToQsEnabled)
@@ -359,9 +325,7 @@
                 )
             )
             keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_AOD,
-                )
+                DozeTransitionModel(to = DozeStateModel.DOZE_AOD)
             )
             val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
             assertThat(isShadeTouchable).isFalse()
@@ -385,9 +349,7 @@
                 )
             )
             keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_PULSING,
-                )
+                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING)
             )
             val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
             assertThat(isShadeTouchable).isTrue()
@@ -450,71 +412,9 @@
                 lastSleepReason = WakeSleepReason.OTHER,
             )
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
+                TransitionStep(transitionState = TransitionState.STARTED)
             )
             val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
             assertThat(isShadeTouchable).isTrue()
         }
-
-    @Test
-    @DisableFlags(DualShade.FLAG_NAME)
-    fun legacyShadeMode_narrowScreen_singleShade() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-            kosmos.shadeRepository.setShadeLayoutWide(false)
-
-            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
-        }
-
-    @Test
-    @DisableFlags(DualShade.FLAG_NAME)
-    fun legacyShadeMode_wideScreen_splitShade() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-            kosmos.shadeRepository.setShadeLayoutWide(true)
-
-            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-        }
-
-    @Test
-    @EnableFlags(DualShade.FLAG_NAME)
-    fun shadeMode_wideScreen_isDual() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-            kosmos.shadeRepository.setShadeLayoutWide(true)
-
-            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
-        }
-
-    @Test
-    @EnableFlags(DualShade.FLAG_NAME)
-    fun shadeMode_narrowScreen_isDual() =
-        testScope.runTest {
-            val shadeMode by collectLastValue(underTest.shadeMode)
-            kosmos.shadeRepository.setShadeLayoutWide(false)
-
-            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
-        }
-
-    @Test
-    fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() =
-        testScope.runTest {
-            // Ensure isShadeLayoutWide is collected.
-            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
-            shadeRepository.setShadeLayoutWide(false)
-
-            assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
-        }
-
-    @Test
-    fun getTopEdgeSplitFraction_wideScreen_leftSideLarger() =
-        testScope.runTest {
-            // Ensure isShadeLayoutWide is collected.
-            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
-            shadeRepository.setShadeLayoutWide(true)
-
-            assertThat(underTest.getTopEdgeSplitFraction()).isGreaterThan(0.5f)
-        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
new file mode 100644
index 0000000..2a2817b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeModeInteractorImplTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: ShadeModeInteractor
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.shadeModeInteractor
+    }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun legacyShadeMode_narrowScreen_singleShade() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun legacyShadeMode_wideScreen_splitShade() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun shadeMode_wideScreen_isDual() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun shadeMode_narrowScreen_isDual() =
+        testScope.runTest {
+            val shadeMode by collectLastValue(underTest.shadeMode)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+        }
+
+    @Test
+    fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() =
+        testScope.runTest {
+            // Ensure isShadeLayoutWide is collected.
+            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
+        }
+
+    @Test
+    fun getTopEdgeSplitFraction_wideScreen_leftSideLarger() =
+        testScope.runTest {
+            // Ensure isShadeLayoutWide is collected.
+            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            assertThat(underTest.getTopEdgeSplitFraction()).isGreaterThan(0.5f)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/VersionInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationListenerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationListenerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index b9ca8fc..c0a1592 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -78,7 +78,7 @@
     @Test
     fun ssid_inactiveNetwork_outputsNull() =
         testScope.runTest {
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
 
             var latest: String? = "default"
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
@@ -93,7 +93,7 @@
     fun ssid_carrierMergedNetwork_outputsNull() =
         testScope.runTest {
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(subscriptionId = 2, level = 1)
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = 2, level = 1)
             )
 
             var latest: String? = "default"
@@ -109,7 +109,7 @@
     fun ssid_unknownSsid_outputsNull() =
         testScope.runTest {
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 1,
                     ssid = WifiManager.UNKNOWN_SSID,
                 )
@@ -128,7 +128,7 @@
     fun ssid_validSsid_outputsSsid() =
         testScope.runTest {
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 1,
                     ssid = "MyAwesomeWifiNetwork",
                 )
@@ -189,7 +189,7 @@
     fun wifiNetwork_matchesRepoWifiNetwork() =
         testScope.runTest {
             val wifiNetwork =
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     isValidated = true,
                     level = 3,
                     ssid = "AB",
@@ -263,7 +263,7 @@
             val latest by collectLastValue(underTest.areNetworksAvailable)
 
             wifiRepository.wifiScanResults.value = emptyList()
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
 
             assertThat(latest).isFalse()
         }
@@ -280,7 +280,7 @@
                     WifiScanEntry(ssid = "ssid 3"),
                 )
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
 
             assertThat(latest).isTrue()
         }
@@ -298,7 +298,7 @@
                 )
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     ssid = "ssid 2",
                     level = 2,
                 )
@@ -318,7 +318,7 @@
                 )
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     ssid = "ssid 2",
                     level = 2,
                 )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 72a45b9..141e304 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -115,7 +115,7 @@
             val latestKeyguard by collectLastValue(keyguard.wifiIcon)
             val latestQs by collectLastValue(qs.wifiIcon)
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 1))
 
             assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
             assertThat(latestHome).isEqualTo(latestKeyguard)
@@ -129,7 +129,7 @@
 
             // Even WHEN the network has a valid hotspot type
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     isValidated = true,
                     level = 1,
                     hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
@@ -191,7 +191,7 @@
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(ssid = null, level = 1))
 
             val activityIn by collectLastValue(underTest.isActivityInViewVisible)
             val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
@@ -214,7 +214,7 @@
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(ssid = null, level = 1))
 
             val activityIn by collectLastValue(underTest.isActivityInViewVisible)
             val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
@@ -463,6 +463,6 @@
     }
 
     companion object {
-        private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(ssid = "AB", level = 1)
+        private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active.of(ssid = "AB", level = 1)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ClockTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ClockTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
diff --git a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
index 3b3ed39..91cd019 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
@@ -215,17 +215,4 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintVertical_bias="1.0"
         tools:srcCompat="@tools:sample/avatars" />
-
-    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-        android:id="@+id/biometric_icon_overlay"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_gravity="center"
-        android:contentDescription="@null"
-        android:scaleType="fitXY"
-        android:importantForAccessibility="no"
-        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
-        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
-        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
index 2a00495..51117a7 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
@@ -40,19 +40,6 @@
         app:layout_constraintTop_toTopOf="parent"
         tools:srcCompat="@tools:sample/avatars" />
 
-    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-        android:id="@+id/biometric_icon_overlay"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_gravity="center"
-        android:contentDescription="@null"
-        android:scaleType="fitXY"
-        android:importantForAccessibility="no"
-        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
-        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
-        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
-        app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
-
     <ScrollView
         android:id="@+id/scrollView"
         android:layout_width="0dp"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1fb1dad..589d9dd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3812,4 +3812,33 @@
     <!-- Toast message for notifying users to use regular brightness bar to lower the brightness. [CHAR LIMIT=NONE] -->
     <string name="accessibility_deprecate_extra_dim_dialog_toast">
         Extra dim shortcut removed. To lower your brightness, use the regular brightness bar.</string>
+
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to connectivity, e.g. Internet. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_connectivity">
+        Connectivity
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to accessibility functions, e.g. Hearing devices. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_accessibility">
+        Accessibility
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to general utilities, e.g. Flashlight. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_utilities">
+        Utilities
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to privacy, e.g. Mic access. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_privacy">
+        Privacy
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are provided by an app, e.g. Calculator. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_providedByApps">
+        Provided by apps
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to the display, e.g. Dark theme. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_display">
+        Display
+    </string>
+    <!-- Label for category in QS Edit mode list of tiles, for tiles with an unknown category. [CHAR LIMIT=NONE] -->
+    <string name="qs_edit_mode_category_unknown">
+        Unknown
+    </string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
index 57a49c8..3e39ae9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
@@ -15,10 +15,7 @@
  */
 package com.android.systemui.dagger.qualifiers
 
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy.RUNTIME
 import javax.inject.Qualifier
 
 /** Annotates a class that is display specific. */
-@Qualifier @Documented @Retention(RUNTIME) annotation class DisplaySpecific
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class DisplaySpecific
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index c00007b..283e455 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -132,4 +132,9 @@
      * Sent when {@link TaskbarDelegate#transitionTo} is called.
      */
     void transitionTo(int barMode, boolean animate) = 33;
+
+    /**
+     * Sent when {@link TaskbarDelegate#appTransitionPending} is called.
+     */
+    void appTransitionPending(boolean pending) = 34;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
index 2c97d62..4d5e717 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
@@ -28,6 +28,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -68,8 +69,9 @@
     }
 
     @Inject
-    public AccessibilityButtonModeObserver(Context context, UserTracker userTracker) {
-        super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
+    public AccessibilityButtonModeObserver(
+            Context context, UserTracker userTracker, SecureSettings secureSettings) {
+        super(context, userTracker, secureSettings, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
index 53a21b3..1363b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
@@ -49,8 +50,9 @@
     }
 
     @Inject
-    public AccessibilityButtonTargetsObserver(Context context, UserTracker userTracker) {
-        super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+    public AccessibilityButtonTargetsObserver(
+            Context context, UserTracker userTracker, SecureSettings secureSettings) {
+        super(context, userTracker, secureSettings, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
index c944878..736217a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
@@ -49,8 +50,9 @@
     }
 
     @Inject
-    public AccessibilityGestureTargetsObserver(Context context, UserTracker userTracker) {
-        super(context, userTracker, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+    public AccessibilityGestureTargetsObserver(
+            Context context, UserTracker userTracker, SecureSettings secureSettings) {
+        super(context, userTracker, secureSettings, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
index 326773f..c50cf85 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,6 +49,7 @@
     private final UserTracker mUserTracker;
     @VisibleForTesting
     final ContentObserver mContentObserver;
+    private final SecureSettings mSecureSettings;
 
     private final String mKey;
 
@@ -55,7 +57,7 @@
     final List<T> mListeners = new ArrayList<>();
 
     protected SecureSettingsContentObserver(Context context, UserTracker userTracker,
-            String secureSettingsKey) {
+            SecureSettings secureSettings, String secureSettingsKey) {
         mKey = secureSettingsKey;
         mContentResolver = context.getContentResolver();
         mUserTracker = userTracker;
@@ -65,6 +67,7 @@
                 updateValueChanged();
             }
         };
+        mSecureSettings = secureSettings;
     }
 
     /**
@@ -80,9 +83,8 @@
         }
 
         if (mListeners.size() == 1) {
-            mContentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(mKey), /* notifyForDescendants= */
-                    false, mContentObserver, UserHandle.USER_ALL);
+            mSecureSettings.registerContentObserverForUserAsync(Settings.Secure.getUriFor(mKey),
+                    /* notifyForDescendants= */ false, mContentObserver, UserHandle.USER_ALL);
         }
     }
 
@@ -97,7 +99,7 @@
         mListeners.remove(listener);
 
         if (mListeners.isEmpty()) {
-            mContentResolver.unregisterContentObserver(mContentObserver);
+            mSecureSettings.unregisterContentObserverAsync(mContentObserver);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7750f6b..51c5b00 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -18,8 +18,6 @@
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
 
-import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-
 import android.accessibilityservice.AccessibilityService;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
@@ -45,7 +43,6 @@
 import android.view.accessibility.Flags;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ScreenshotHelper;
@@ -561,16 +558,13 @@
     }
 
     private void handleAccessibilityButton() {
-        AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
+        mA11yManager.notifyAccessibilityButtonClicked(
                 mDisplayTracker.getDefaultDisplayId());
     }
 
     private void handleAccessibilityButtonChooser() {
-        final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
-        intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
+        mA11yManager.notifyAccessibilityButtonLongClicked(
+                mDisplayTracker.getDefaultDisplayId());
     }
 
     private void handleAccessibilityShortcut() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index bb80396..cd9efaf 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.ColorCorrectionTile
 import com.android.systemui.qs.tiles.ColorInversionTile
@@ -179,6 +180,7 @@
                         labelRes = R.string.quick_settings_color_correction_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.ACCESSIBILITY,
             )
 
         /** Inject ColorCorrectionTile into tileViewModelMap in QSModule */
@@ -210,6 +212,7 @@
                         labelRes = R.string.quick_settings_inversion_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.ACCESSIBILITY,
             )
 
         /** Inject ColorInversionTile into tileViewModelMap in QSModule */
@@ -241,6 +244,7 @@
                         labelRes = R.string.quick_settings_font_scaling_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         /** Inject FontScaling Tile into tileViewModelMap in QSModule */
@@ -272,6 +276,7 @@
                         labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         @Provides
@@ -286,6 +291,7 @@
                         labelRes = R.string.quick_settings_hearing_devices_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.ACCESSIBILITY,
             )
 
         /**
@@ -322,6 +328,7 @@
                         labelRes = R.string.quick_settings_onehanded_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.ACCESSIBILITY,
             )
 
         /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */
@@ -355,6 +362,7 @@
                         labelRes = R.string.quick_settings_night_display_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8a9a322..831bc1d 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.BatterySaverTile
 import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
@@ -33,7 +34,7 @@
     @IntoMap
     @StringKey(BATTERY_SAVER_TILE_SPEC)
     fun provideBatterySaverAvailabilityInteractor(
-            impl: BatterySaverTileDataInteractor
+        impl: BatterySaverTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     companion object {
@@ -51,6 +52,7 @@
                         labelRes = R.string.battery_detail_switch_title,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
             )
 
         /** Inject BatterySaverTile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index ec3fd9f..7ecbb88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
@@ -46,6 +48,12 @@
 
     @Binds
     @SysUISingleton
+    fun providesSideFpsOverlayInteractor(
+        impl: SideFpsOverlayInteractorImpl
+    ): SideFpsOverlayInteractor
+
+    @Binds
+    @SysUISingleton
     fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
new file mode 100644
index 0000000..10c3483
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.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.biometrics.domain.interactor
+
+import android.util.Log
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.NotRunning
+import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
+
+/** Encapsulates business logic for showing and hiding the side fingerprint sensor indicator. */
+interface SideFpsOverlayInteractor {
+    /** Whether the side fingerprint sensor indicator is currently showing. */
+    val isShowing: Flow<Boolean>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class SideFpsOverlayInteractorImpl
+@Inject
+constructor(
+    biometricStatusInteractor: BiometricStatusInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
+    sfpsSensorInteractor: SideFpsSensorInteractor,
+    // TODO(b/365182034): add progress bar input when rest to unlock feature is implemented
+) : SideFpsOverlayInteractor {
+    private val sfpsOverlayEnabled: Flow<Boolean> =
+        sfpsSensorInteractor.isAvailable.sample(displayStateInteractor.isInRearDisplayMode) {
+            isAvailable: Boolean,
+            isInRearDisplayMode: Boolean ->
+            isAvailable && !isInRearDisplayMode
+        }
+
+    private val showSideFpsOverlay: Flow<Boolean> =
+        combine(
+            biometricStatusInteractor.sfpsAuthenticationReason,
+            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+            // TODO(b/365182034): add progress bar input when rest to unlock feature is implemented
+        ) { systemServerAuthReason, showIndicatorForDeviceEntry ->
+            Log.d(
+                TAG,
+                "systemServerAuthReason = $systemServerAuthReason, " +
+                    "showIndicatorForDeviceEntry = $showIndicatorForDeviceEntry, "
+            )
+            systemServerAuthReason != NotRunning || showIndicatorForDeviceEntry
+        }
+
+    override val isShowing: Flow<Boolean> =
+        sfpsOverlayEnabled
+            .flatMapLatest { sfpsOverlayEnabled ->
+                if (!sfpsOverlayEnabled) {
+                    flowOf(false)
+                } else {
+                    showSideFpsOverlay
+                }
+            }
+            .onEach { Log.d(TAG, "isShowing: $it") }
+
+    companion object {
+        private const val TAG = "SideFpsOverlayInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d69e875..d055731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -158,16 +158,13 @@
             fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
                 viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
                 largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                 largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
                 largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
 
                 if (hideSensorIcon) {
                     smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                    smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                     smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
                     mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE)
-                    mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                     mediumConstraintSet.setVisibility(R.id.indicator, View.GONE)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9578da4..9fe1dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -33,89 +33,44 @@
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardPINView
 import com.android.systemui.CoreStartable
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
-import com.android.systemui.biometrics.shared.model.AuthenticationReason.NotRunning
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.LottieCallback
 import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
-import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SideFpsOverlayViewBinder
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
-    private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
-    private val displayStateInteractor: Lazy<DisplayStateInteractor>,
-    private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
     private val layoutInflater: Lazy<LayoutInflater>,
-    private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
-    private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+    private val sideFpsOverlayInteractor: Lazy<SideFpsOverlayInteractor>,
+    private val sideFpsOverlayViewModel: Lazy<SideFpsOverlayViewModel>,
     private val windowManager: Lazy<WindowManager>
 ) : CoreStartable {
+    private var overlayView: View? = null
 
     override fun start() {
-        applicationScope
-            .launch {
-                sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
-                    if (isSfpsAvailable) {
-                        combine(
-                                biometricStatusInteractor.get().sfpsAuthenticationReason,
-                                deviceEntrySideFpsOverlayInteractor
-                                    .get()
-                                    .showIndicatorForDeviceEntry,
-                                sideFpsProgressBarViewModel.get().isVisible,
-                                ::Triple
-                            )
-                            .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
-                            .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
-                                val (
-                                    systemServerAuthReason,
-                                    showIndicatorForDeviceEntry,
-                                    progressBarIsVisible) =
-                                    combinedFlows
-                                Log.d(
-                                    TAG,
-                                    "systemServerAuthReason = $systemServerAuthReason, " +
-                                        "showIndicatorForDeviceEntry = " +
-                                        "$showIndicatorForDeviceEntry, " +
-                                        "progressBarIsVisible = $progressBarIsVisible"
-                                )
-                                if (!isInRearDisplayMode) {
-                                    if (progressBarIsVisible) {
-                                        hide()
-                                    } else if (systemServerAuthReason != NotRunning) {
-                                        show()
-                                    } else if (showIndicatorForDeviceEntry) {
-                                        show()
-                                    } else {
-                                        hide()
-                                    }
-                                }
-                            }
-                    }
+        applicationScope.launch {
+            sideFpsOverlayInteractor.get().isShowing.collect { isShowing: Boolean ->
+                if (isShowing) {
+                    show()
+                } else {
+                    hide()
                 }
             }
+        }
     }
 
-    private var overlayView: View? = null
-
     /** Show the side fingerprint sensor indicator */
     private fun show() {
         if (overlayView?.isAttachedToWindow == true) {
@@ -125,17 +80,10 @@
             )
             return
         }
-
         overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
-
-        val overlayViewModel =
-            SideFpsOverlayViewModel(
-                applicationContext,
-                deviceEntrySideFpsOverlayInteractor.get(),
-                displayStateInteractor.get(),
-                sfpsSensorInteractor.get(),
-            )
+        val overlayViewModel = sideFpsOverlayViewModel.get()
         bind(overlayView!!, overlayViewModel, windowManager.get())
+
         overlayView!!.visibility = View.INVISIBLE
         Log.d(TAG, "show(): adding overlayView $overlayView")
         windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
@@ -161,6 +109,20 @@
     companion object {
         private const val TAG = "SideFpsOverlayViewBinder"
 
+        private val accessibilityDelegate =
+            object : View.AccessibilityDelegate() {
+                override fun dispatchPopulateAccessibilityEvent(
+                    host: View,
+                    event: AccessibilityEvent
+                ): Boolean {
+                    return if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+                        true
+                    } else {
+                        super.dispatchPopulateAccessibilityEvent(host, event)
+                    }
+                }
+            }
+
         /** Binds overlayView (side fingerprint sensor indicator view) to SideFpsOverlayViewModel */
         fun bind(
             overlayView: View,
@@ -184,24 +146,7 @@
 
                 overlayShowAnimator.start()
 
-                it.setAccessibilityDelegate(
-                    object : View.AccessibilityDelegate() {
-                        override fun dispatchPopulateAccessibilityEvent(
-                            host: View,
-                            event: AccessibilityEvent
-                        ): Boolean {
-                            return if (
-                                event.getEventType() ===
-                                    android.view.accessibility.AccessibilityEvent
-                                        .TYPE_WINDOW_STATE_CHANGED
-                            ) {
-                                true
-                            } else {
-                                super.dispatchPopulateAccessibilityEvent(host, event)
-                            }
-                        }
-                    }
-                )
+                it.accessibilityDelegate = accessibilityDelegate
 
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 168ba11..85f221f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,11 +78,11 @@
 class PromptViewModel
 @Inject
 constructor(
-    displayStateInteractor: DisplayStateInteractor,
+    private val displayStateInteractor: DisplayStateInteractor,
     private val promptSelectorInteractor: PromptSelectorInteractor,
     @Application private val context: Context,
-    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
-    private val biometricStatusInteractor: BiometricStatusInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
+    biometricStatusInteractor: BiometricStatusInteractor,
     private val udfpsUtils: UdfpsUtils,
     private val iconProvider: IconProvider,
     private val activityTaskManager: ActivityTaskManager,
@@ -135,11 +135,13 @@
             R.dimen.biometric_prompt_landscape_medium_horizontal_padding
         )
 
+    val currentRotation: StateFlow<DisplayRotation> = displayStateInteractor.currentRotation
+
     val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
         udfpsOverlayInteractor.udfpsOverlayParams
 
     private val udfpsSensorBounds: Flow<Rect> =
-        combine(udfpsOverlayParams, displayStateInteractor.currentRotation) { params, rotation ->
+        combine(udfpsOverlayParams, currentRotation) { params, rotation ->
                 val rotatedBounds = Rect(params.sensorBounds)
                 RotationUtils.rotateBounds(
                     rotatedBounds,
@@ -262,7 +264,7 @@
                 _forceLargeSize,
                 promptKind,
                 displayStateInteractor.isLargeScreen,
-                displayStateInteractor.currentRotation,
+                currentRotation,
                 modalities
             ) { forceLarge, promptKind, isLargeScreen, rotation, modalities ->
                 when {
@@ -454,7 +456,7 @@
 
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
-        combine(size, displayStateInteractor.currentRotation) { size, rotation ->
+        combine(size, currentRotation) { size, rotation ->
             if (size != PromptSize.LARGE) {
                 val navBarInsets = Utils.getNavbarInsets(context)
                 if (rotation == DisplayRotation.ROTATION_90) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index c2a4ee3..7c1984e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -147,8 +147,7 @@
             _lottieBounds,
             sensorLocation,
             displayRotation,
-        ) { bounds: Rect?, sensorLocation: SideFpsSensorLocation, displayRotation: DisplayRotation
-            ->
+        ) { _: Rect?, sensorLocation: SideFpsSensorLocation, _: DisplayRotation ->
             val topLeft = Point(sensorLocation.left, sensorLocation.top)
 
             defaultOverlayViewParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index a1111f6..d7a4863b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -21,6 +21,10 @@
 
 object ComposeBouncerFlags {
 
+    /** @see [isComposeBouncerOrSceneContainerEnabled] */
+    val isEnabled: Boolean
+        get() = isComposeBouncerOrSceneContainerEnabled()
+
     /**
      * Returns `true` if the Compose bouncer is enabled or if the scene container framework is
      * enabled; `false` otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index db7ffc1..037b6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.DeviceControlsTile
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
@@ -86,15 +87,16 @@
         @IntoMap
         @StringKey(DEVICE_CONTROLS_SPEC)
         fun provideDeviceControlsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
-                QSTileConfig(
-                        tileSpec = TileSpec.create(DEVICE_CONTROLS_SPEC),
-                        uiConfig =
-                        QSTileUIConfig.Resource(
-                                iconRes = com.android.systemui.res.R.drawable.controls_icon,
-                                labelRes = com.android.systemui.res.R.string.quick_controls_title
-                        ),
-                        instanceId = uiEventLogger.getNewInstanceId(),
-                )
+            QSTileConfig(
+                tileSpec = TileSpec.create(DEVICE_CONTROLS_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = com.android.systemui.res.R.drawable.controls_icon,
+                        labelRes = com.android.systemui.res.R.string.quick_controls_title
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
+            )
     }
 
     @Binds
@@ -115,12 +117,12 @@
 
     @Binds
     abstract fun provideSettingsManager(
-            manager: ControlsSettingsRepositoryImpl
+        manager: ControlsSettingsRepositoryImpl
     ): ControlsSettingsRepository
 
     @Binds
     abstract fun provideDialogManager(
-            manager: ControlsSettingsDialogManagerImpl
+        manager: ControlsSettingsDialogManagerImpl
     ): ControlsSettingsDialogManager
 
     @Binds
@@ -141,8 +143,7 @@
         repository: SelectedComponentRepositoryImpl
     ): SelectedComponentRepository
 
-    @BindsOptionalOf
-    abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+    @BindsOptionalOf abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
 
     @BindsOptionalOf
     abstract fun provideControlsTileResourceConfiguration(): ControlsTileResourceConfiguration
@@ -157,23 +158,17 @@
     @Binds
     @IntoMap
     @ClassKey(ControlsFavoritingActivity::class)
-    abstract fun provideControlsFavoritingActivity(
-        activity: ControlsFavoritingActivity
-    ): Activity
+    abstract fun provideControlsFavoritingActivity(activity: ControlsFavoritingActivity): Activity
 
     @Binds
     @IntoMap
     @ClassKey(ControlsEditingActivity::class)
-    abstract fun provideControlsEditingActivity(
-        activity: ControlsEditingActivity
-    ): Activity
+    abstract fun provideControlsEditingActivity(activity: ControlsEditingActivity): Activity
 
     @Binds
     @IntoMap
     @ClassKey(ControlsRequestDialog::class)
-    abstract fun provideControlsRequestDialog(
-        activity: ControlsRequestDialog
-    ): Activity
+    abstract fun provideControlsRequestDialog(activity: ControlsRequestDialog): Activity
 
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index f6ac7a5..a45ad15 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -38,6 +38,7 @@
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
+import com.android.systemui.qs.shared.model.TileCategory;
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig;
 import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy;
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig;
@@ -196,6 +197,7 @@
                         R.drawable.ic_qs_screen_saver,
                         R.string.quick_settings_screensaver_label),
                 uiEventLogger.getNewInstanceId(),
+                TileCategory.UTILITIES,
                 tileSpec.getSpec(),
                 QSTilePolicy.NoRestrictions.INSTANCE
                 );
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 3223433..eb4eee2 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -16,11 +16,17 @@
 
 package com.android.systemui.education.domain.interactor
 
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /**
@@ -39,12 +45,17 @@
 @Inject
 constructor(
     @Background private val backgroundScope: CoroutineScope,
-    private val contextualEducationInteractor: ContextualEducationInteractor
+    private val contextualEducationInteractor: ContextualEducationInteractor,
+    private val inputDeviceRepository: UserInputDeviceRepository,
 ) : KeyboardTouchpadEduStatsInteractor {
 
     override fun incrementSignalCount(gestureType: GestureType) {
-        // Todo: check if keyboard/touchpad is connected before update
-        backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
+        backgroundScope.launch {
+            val targetDevice = getTargetDevice(gestureType)
+            if (isTargetDeviceConnected(targetDevice)) {
+                contextualEducationInteractor.incrementSignalCount(gestureType)
+            }
+        }
     }
 
     override fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -52,4 +63,24 @@
             contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
         }
     }
+
+    private suspend fun isTargetDeviceConnected(deviceType: DeviceType): Boolean {
+        if (deviceType == KEYBOARD) {
+            return inputDeviceRepository.isAnyKeyboardConnectedForUser.first().isConnected
+        } else if (deviceType == TOUCHPAD) {
+            return inputDeviceRepository.isAnyTouchpadConnectedForUser.first().isConnected
+        }
+        return false
+    }
+
+    /**
+     * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would
+     * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps
+     * gesture to its target education device.
+     */
+    private fun getTargetDevice(gestureType: GestureType) =
+        when (gestureType) {
+            ALL_APPS -> KEYBOARD
+            else -> TOUCHPAD
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 7ecacdc..092a25a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -16,9 +16,17 @@
 
 package com.android.systemui.inputdevice.tutorial
 
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
 import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
 import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
 import dagger.Lazy
 import javax.inject.Inject
@@ -27,11 +35,35 @@
 @SysUISingleton
 class KeyboardTouchpadTutorialCoreStartable
 @Inject
-constructor(private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>) :
-    CoreStartable {
+constructor(
+    private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    @Application private val applicationContext: Context,
+) : CoreStartable {
     override fun start() {
         if (newTouchpadGesturesTutorial()) {
             tutorialNotificationCoordinator.get().start()
+            registerTutorialBroadcastReceiver()
         }
     }
+
+    private fun registerTutorialBroadcastReceiver() {
+        broadcastDispatcher.registerReceiver(
+            receiver =
+                object : BroadcastReceiver() {
+                    override fun onReceive(context: Context, intent: Intent) {
+                        applicationContext.startActivityAsUser(
+                            Intent(
+                                applicationContext,
+                                KeyboardTouchpadTutorialActivity::class.java
+                            ),
+                            UserHandle.SYSTEM
+                        )
+                    }
+                },
+            filter = IntentFilter("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"),
+            flags = Context.RECEIVER_EXPORTED,
+            user = UserHandle.ALL,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 63f3d52..dcca12f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -82,6 +82,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.geometry.CornerRadius
@@ -92,8 +93,12 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
@@ -824,9 +829,18 @@
     // from the ViewModel.
     var queryInternal by remember { mutableStateOf("") }
     val focusRequester = remember { FocusRequester() }
+    val focusManager = LocalFocusManager.current
     LaunchedEffect(Unit) { focusRequester.requestFocus() }
     SearchBar(
-        modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
+        modifier =
+            Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent {
+                if (it.key == Key.DirectionDown) {
+                    focusManager.moveFocus(FocusDirection.Down)
+                    return@onKeyEvent true
+                } else {
+                    return@onKeyEvent false
+                }
+            },
         colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
         query = queryInternal,
         active = false,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 362e016c..df0f10a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.google.android.msdl.domain.MSDLPlayer
 import dagger.Lazy
@@ -112,6 +113,7 @@
     private val clockInteractor: KeyguardClockInteractor,
     private val keyguardViewMediator: KeyguardViewMediator,
     private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val msdlPlayer: MSDLPlayer,
 ) : CoreStartable {
@@ -220,6 +222,7 @@
                 vibratorHelper,
                 falsingManager,
                 keyguardViewMediator,
+                statusBarKeyguardViewManager,
                 mainDispatcher,
                 msdlPlayer,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index e2bb540..7afc759 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -80,10 +80,7 @@
         }
         applicationScope.launch {
             val refreshConfig =
-                Config(
-                    Type.NoTransition,
-                    rebuildSections = listOf(smartspaceSection),
-                )
+                Config(Type.NoTransition, rebuildSections = listOf(smartspaceSection))
             configurationInteractor.onAnyConfigurationChange.collect {
                 refreshBlueprint(refreshConfig)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 7801c00..60c5386 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolver
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -44,6 +46,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.stateIn
@@ -58,12 +61,14 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
-    sceneInteractor: SceneInteractor,
-    deviceEntryInteractor: DeviceEntryInteractor,
-    quickSettingsSceneFamilyResolver: QuickSettingsSceneFamilyResolver,
-    notifShadeSceneFamilyResolver: NotifShadeSceneFamilyResolver,
+    sceneInteractor: dagger.Lazy<SceneInteractor>,
+    deviceEntryInteractor: dagger.Lazy<DeviceEntryInteractor>,
+    quickSettingsSceneFamilyResolver: dagger.Lazy<QuickSettingsSceneFamilyResolver>,
+    notifShadeSceneFamilyResolver: dagger.Lazy<NotifShadeSceneFamilyResolver>,
     powerInteractor: PowerInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
+    keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+    shadeInteractor: dagger.Lazy<ShadeInteractor>,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -98,15 +103,31 @@
      * device is unlocked. Else, false.
      */
     private val isOnShadeWhileUnlocked: Flow<Boolean> =
-        combine(
-                sceneInteractor.currentScene,
-                deviceEntryInteractor.isUnlocked,
-            ) { scene, isUnlocked ->
-                isUnlocked &&
-                    (quickSettingsSceneFamilyResolver.includesScene(scene) ||
-                        notifShadeSceneFamilyResolver.includesScene(scene))
+        if (SceneContainerFlag.isEnabled) {
+            combine(
+                    sceneInteractor.get().currentScene,
+                    deviceEntryInteractor.get().isUnlocked,
+                ) { scene, isUnlocked ->
+                    isUnlocked &&
+                        (quickSettingsSceneFamilyResolver.get().includesScene(scene) ||
+                            notifShadeSceneFamilyResolver.get().includesScene(scene))
+                }
+                .distinctUntilChanged()
+        } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
+            shadeInteractor.get().isAnyExpanded.sample(
+                keyguardInteractor.get().isKeyguardDismissible
+            ) { isAnyExpanded, isKeyguardDismissible ->
+                isAnyExpanded && isKeyguardDismissible
             }
-            .distinctUntilChanged()
+        } else {
+            flow {
+                error(
+                    "This should not be used when both SceneContainerFlag " +
+                        "and ComposeBouncerFlag are disabled"
+                )
+            }
+        }
+
     val executeDismissAction: Flow<() -> KeyguardDone> =
         merge(
                 finishedTransitionToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index 74a7262..69cb6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -17,11 +17,11 @@
 
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
@@ -41,7 +41,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        if (!SceneContainerFlag.isEnabled) {
+        if (!ComposeBouncerFlags.isEnabled) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index ac24591..b55f813 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -18,13 +18,12 @@
 import com.android.keyguard.ViewMediatorCallback
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -40,11 +39,10 @@
     private val viewMediatorCallback: ViewMediatorCallback,
     @Application private val scope: CoroutineScope,
     private val keyguardLogger: KeyguardLogger,
-    private val featureFlags: FeatureFlagsClassic,
 ) : CoreStartable {
 
     override fun start() {
-        if (!SceneContainerFlag.isEnabled) {
+        if (!ComposeBouncerFlags.isEnabled) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index f2d39da..ecfabc3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -18,13 +18,12 @@
 
 import android.annotation.SuppressLint
 import android.graphics.PointF
+import android.view.InputDevice
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
 import android.view.ViewPropertyAnimator
-import androidx.core.animation.CycleInterpolator
-import androidx.core.animation.ObjectAnimator
-import com.android.systemui.res.R
+import com.android.systemui.Flags
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -71,11 +70,10 @@
                     // Moving too far while performing a long-press gesture cancels that
                     // gesture.
                     if (
-                        event
-                            .rawDistanceFrom(
-                                downDisplayCoords.x,
-                                downDisplayCoords.y,
-                            ) > ViewConfiguration.getTouchSlop()
+                        event.rawDistanceFrom(
+                            downDisplayCoords.x,
+                            downDisplayCoords.y,
+                        ) > ViewConfiguration.getTouchSlop()
                     ) {
                         cancel()
                     }
@@ -151,10 +149,14 @@
             event: MotionEvent,
             pointerIndex: Int = 0,
         ): Boolean {
-            return when (event.getToolType(pointerIndex)) {
-                MotionEvent.TOOL_TYPE_STYLUS -> true
-                MotionEvent.TOOL_TYPE_MOUSE -> true
-                else -> false
+            return if (Flags.nonTouchscreenDevicesBypassFalsing()) {
+                event.device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == false
+            } else {
+                when (event.getToolType(pointerIndex)) {
+                    MotionEvent.TOOL_TYPE_STYLUS -> true
+                    MotionEvent.TOOL_TYPE_MOUSE -> true
+                    else -> false
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 5bb7b64..ed82159 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -24,6 +24,8 @@
 import android.graphics.Rect
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import android.view.InputDevice
+import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnLayoutChangeListener
 import android.view.View.VISIBLE
@@ -41,6 +43,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
 import com.android.systemui.Flags.msdlFeedback
 import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.Icon
@@ -73,6 +76,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
@@ -115,6 +119,7 @@
         vibratorHelper: VibratorHelper?,
         falsingManager: FalsingManager?,
         keyguardViewMediator: KeyguardViewMediator?,
+        statusBarKeyguardViewManager: StatusBarKeyguardViewManager?,
         mainImmediateDispatcher: CoroutineDispatcher,
         msdlPlayer: MSDLPlayer?,
     ): DisposableHandle {
@@ -124,12 +129,30 @@
         if (KeyguardBottomAreaRefactor.isEnabled) {
             disposables +=
                 view.onTouchListener { _, event ->
+                    var consumed = false
                     if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+                        // signifies a primary button click down has reached keyguardrootview
+                        // we need to return true here otherwise an ACTION_UP will never arrive
+                        if (Flags.nonTouchscreenDevicesBypassFalsing()) {
+                            if (
+                                event.action == MotionEvent.ACTION_DOWN &&
+                                    event.buttonState == MotionEvent.BUTTON_PRIMARY &&
+                                    !event.isTouchscreenSource()
+                            ) {
+                                consumed = true
+                            } else if (
+                                event.action == MotionEvent.ACTION_UP &&
+                                    !event.isTouchscreenSource()
+                            ) {
+                                statusBarKeyguardViewManager?.showBouncer(true)
+                                consumed = true
+                            }
+                        }
                         viewModel.setRootViewLastTapPosition(
                             Point(event.x.toInt(), event.y.toInt())
                         )
                     }
-                    false
+                    consumed
                 }
         }
 
@@ -637,6 +660,10 @@
         }
     }
 
+    private fun MotionEvent.isTouchscreenSource(): Boolean {
+        return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true
+    }
+
     private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
         setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index f581a2e..0b8f741 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -417,6 +417,7 @@
                     null, // device entry haptics not required for preview mode
                     null, // falsing manager not required for preview mode
                     null, // keyguard view mediator is not required for preview mode
+                    null, // primary bouncer interactor is not required for preview mode
                     mainDispatcher,
                     null,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 8485a26..1edfec8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -18,6 +18,7 @@
 
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -25,7 +26,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -51,7 +51,7 @@
 ) {
     /** Common fade for scrim alpha values during *BOUNCER->GONE */
     fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
-        return if (SceneContainerFlag.isEnabled) {
+        return if (ComposeBouncerFlags.isEnabled) {
             keyguardDismissActionInteractor
                 .get()
                 .willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index f33752f..12bcc7e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.util.MathUtils
+import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -55,8 +56,18 @@
         var currentAlpha = 0f
         return transitionAnimation.sharedFlow(
             duration = 250.milliseconds,
-            startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
-            onStart = { currentAlpha = viewState.alpha() },
+            startTime = if (lightRevealMigration()) {
+                100.milliseconds // Wait for the light reveal to "hit" the LS elements.
+            } else {
+                0.milliseconds
+            },
+            onStart = {
+                if (lightRevealMigration()) {
+                    currentAlpha = viewState.alpha()
+                } else {
+                    currentAlpha = 0f
+                }
+            },
             onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
             onCancel = { 0f },
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 8811908..17c678e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
@@ -25,7 +26,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -85,7 +85,7 @@
 
     /** Bouncer container alpha */
     val bouncerAlpha: Flow<Float> =
-        if (SceneContainerFlag.isEnabled) {
+        if (ComposeBouncerFlags.isEnabled) {
             keyguardDismissActionInteractor
                 .get()
                 .willAnimateDismissActionOnLockscreen
@@ -110,7 +110,7 @@
 
     /** Lockscreen alpha */
     val lockscreenAlpha: Flow<Float> =
-        if (SceneContainerFlag.isEnabled) {
+        if (ComposeBouncerFlags.isEnabled) {
             keyguardDismissActionInteractor
                 .get()
                 .willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index c5909ed..75e3871 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -107,6 +107,8 @@
                 }
             }
 
+    // TODO(b/365182034): move to interactor, add as dependency of SideFpsOverlayInteractor when
+    //  rest to unlock feature is implemented
     val isVisible: Flow<Boolean> = _visible.asStateFlow()
 
     val progress: Flow<Float> = _progress.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt
index 08df7db..9f893e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.log.dagger
 
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
 import javax.inject.Qualifier
 
 /** Logger for the primary and alternative bouncers. */
-@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerTableLog
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BouncerTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 498c34c..2053b53 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -382,6 +382,16 @@
         return factory.create("MediaLog", 20);
     }
 
+    /**
+     * Provides a buffer for media device changes
+     */
+    @Provides
+    @SysUISingleton
+    @MediaDeviceLog
+    public static LogBuffer providesMediaDeviceLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaDeviceLog", 50);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
new file mode 100644
index 0000000..06bd269
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import com.android.systemui.log.LogBuffer
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaDeviceLogger] */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaDeviceLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 4ad437c..84aae65 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -70,6 +70,7 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.MediaLogger
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
 import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.shared.model.MediaAction
@@ -84,7 +85,7 @@
 import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.SmallHash
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -186,7 +187,6 @@
     private val mediaDeviceManager: MediaDeviceManager,
     mediaDataCombineLatest: MediaDataCombineLatest,
     private val mediaDataFilter: LegacyMediaDataFilterImpl,
-    private val activityStarter: ActivityStarter,
     private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
@@ -197,6 +197,7 @@
     private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
+    private val mediaLogger: MediaLogger,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
 
     companion object {
@@ -273,7 +274,6 @@
         mediaDeviceManager: MediaDeviceManager,
         mediaDataCombineLatest: MediaDataCombineLatest,
         mediaDataFilter: LegacyMediaDataFilterImpl,
-        activityStarter: ActivityStarter,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
         tunerService: TunerService,
@@ -282,6 +282,7 @@
         smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
         mediaDataLoader: dagger.Lazy<MediaDataLoader>,
+        mediaLogger: MediaLogger,
     ) : this(
         context,
         // Loading bitmap for UMO background can take longer time, so it cannot run on the default
@@ -301,7 +302,6 @@
         mediaDeviceManager,
         mediaDataCombineLatest,
         mediaDataFilter,
-        activityStarter,
         smartspaceMediaDataProvider,
         Utils.useMediaResumption(context),
         Utils.useQsMediaPlayer(context),
@@ -312,6 +312,7 @@
         smartspaceManager,
         keyguardUpdateMonitor,
         mediaDataLoader,
+        mediaLogger,
     )
 
     private val appChangeReceiver =
@@ -564,6 +565,42 @@
             val resumeAction: Runnable? = currentEntry?.resumeAction
             val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
             val active = currentEntry?.active ?: true
+            val mediaController = mediaControllerFactory.create(result.token!!)
+
+            val mediaData =
+                MediaData(
+                    userId = sbn.normalizedUserId,
+                    initialized = true,
+                    app = result.appName,
+                    appIcon = result.appIcon,
+                    artist = result.artist,
+                    song = result.song,
+                    artwork = result.artworkIcon,
+                    actions = result.actionIcons,
+                    actionsToShowInCompact = result.actionsToShowInCompact,
+                    semanticActions = result.semanticActions,
+                    packageName = sbn.packageName,
+                    token = result.token,
+                    clickIntent = result.clickIntent,
+                    device = result.device,
+                    active = active,
+                    resumeAction = resumeAction,
+                    playbackLocation = result.playbackLocation,
+                    notificationKey = key,
+                    hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = result.isPlaying,
+                    isClearable = !sbn.isOngoing,
+                    lastActive = lastActive,
+                    createdTimestampMillis = createdTimestampMillis,
+                    instanceId = instanceId,
+                    appUid = result.appUid,
+                    isExplicit = result.isExplicit,
+                )
+
+            if (isSameMediaData(context, mediaController, mediaData, currentEntry)) {
+                mediaLogger.logDuplicateMediaNotification(key)
+                return@withContext
+            }
 
             // We need to log the correct media added.
             if (isNewlyActiveEntry) {
@@ -583,40 +620,7 @@
                 )
             }
 
-            withContext(mainDispatcher) {
-                onMediaDataLoaded(
-                    key,
-                    oldKey,
-                    MediaData(
-                        userId = sbn.normalizedUserId,
-                        initialized = true,
-                        app = result.appName,
-                        appIcon = result.appIcon,
-                        artist = result.artist,
-                        song = result.song,
-                        artwork = result.artworkIcon,
-                        actions = result.actionIcons,
-                        actionsToShowInCompact = result.actionsToShowInCompact,
-                        semanticActions = result.semanticActions,
-                        packageName = sbn.packageName,
-                        token = result.token,
-                        clickIntent = result.clickIntent,
-                        device = result.device,
-                        active = active,
-                        resumeAction = resumeAction,
-                        playbackLocation = result.playbackLocation,
-                        notificationKey = key,
-                        hasCheckedForResume = hasCheckedForResume,
-                        isPlaying = result.isPlaying,
-                        isClearable = !sbn.isOngoing,
-                        lastActive = lastActive,
-                        createdTimestampMillis = createdTimestampMillis,
-                        instanceId = instanceId,
-                        appUid = result.appUid,
-                        isExplicit = result.isExplicit,
-                    )
-                )
-            }
+            withContext(mainDispatcher) { onMediaDataLoaded(key, oldKey, mediaData) }
         }
 
     /** Add a listener for changes in this class */
@@ -1100,6 +1104,47 @@
         val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
         val appUid = appInfo?.uid ?: Process.INVALID_UID
 
+        val lastActive = systemClock.elapsedRealtime()
+        val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+        val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+        val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+        val active = mediaEntries[key]?.active ?: true
+        var mediaData =
+            MediaData(
+                sbn.normalizedUserId,
+                true,
+                appName,
+                smallIcon,
+                artist,
+                song,
+                artWorkIcon,
+                actionIcons,
+                actionsToShowCollapsed,
+                semanticActions,
+                sbn.packageName,
+                token,
+                notif.contentIntent,
+                device,
+                active,
+                resumeAction = resumeAction,
+                playbackLocation = playbackLocation,
+                notificationKey = key,
+                hasCheckedForResume = hasCheckedForResume,
+                isPlaying = isPlaying,
+                isClearable = !sbn.isOngoing,
+                lastActive = lastActive,
+                createdTimestampMillis = createdTimestampMillis,
+                instanceId = instanceId,
+                appUid = appUid,
+                isExplicit = isExplicit,
+                smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
+            )
+
+        if (isSameMediaData(context, mediaController, mediaData, currentEntry)) {
+            mediaLogger.logDuplicateMediaNotification(key)
+            return
+        }
+
         if (isNewlyActiveEntry) {
             logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -1107,44 +1152,17 @@
             logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
         }
 
-        val lastActive = systemClock.elapsedRealtime()
-        val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
         foregroundExecutor.execute {
-            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
-            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
-            val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(
-                key,
-                oldKey,
-                MediaData(
-                    sbn.normalizedUserId,
-                    true,
-                    appName,
-                    smallIcon,
-                    artist,
-                    song,
-                    artWorkIcon,
-                    actionIcons,
-                    actionsToShowCollapsed,
-                    semanticActions,
-                    sbn.packageName,
-                    token,
-                    notif.contentIntent,
-                    device,
-                    active,
-                    resumeAction = resumeAction,
-                    playbackLocation = playbackLocation,
-                    notificationKey = key,
-                    hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying,
-                    isClearable = !sbn.isOngoing,
-                    lastActive = lastActive,
-                    createdTimestampMillis = createdTimestampMillis,
-                    instanceId = instanceId,
-                    appUid = appUid,
-                    isExplicit = isExplicit,
+            val oldResumeAction: Runnable? = mediaEntries[key]?.resumeAction
+            val oldHasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+            val oldActive = mediaEntries[key]?.active ?: true
+            mediaData =
+                mediaData.copy(
+                    resumeAction = oldResumeAction,
+                    hasCheckedForResume = oldHasCheckedForResume,
+                    active = oldActive
                 )
-            )
+            onMediaDataLoaded(key, oldKey, mediaData)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
new file mode 100644
index 0000000..f886166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.media.session.MediaController
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.MediaDeviceLog
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import javax.inject.Inject
+
+/** A [LogBuffer] for media device changes */
+class MediaDeviceLogger @Inject constructor(@MediaDeviceLog private val buffer: LogBuffer) {
+
+    fun logBroadcastEvent(event: String, reason: Int, broadcastId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event
+                int1 = reason
+                int2 = broadcastId
+            },
+            { "$str1, reason = $int1, broadcastId = $int2" }
+        )
+    }
+
+    fun logBroadcastEvent(event: String, reason: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event
+                int1 = reason
+            },
+            { "$str1, reason = $int1" }
+        )
+    }
+
+    fun logBroadcastMetadataChanged(broadcastId: Int, metadata: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = broadcastId
+                str1 = metadata
+            },
+            { "onBroadcastMetadataChanged, broadcastId = $int1, metadata = $str1" }
+        )
+    }
+
+    fun logNewDeviceName(name: String?) {
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = name }, { "New device name $str1" })
+    }
+
+    fun logLocalDevice(sassDevice: MediaDeviceData?, connectedDevice: MediaDeviceData?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = sassDevice?.name?.toString()
+                str2 = connectedDevice?.name?.toString()
+            },
+            { "Local device: $str1 or $str2" }
+        )
+    }
+
+    fun logRemoteDevice(routingSessionName: CharSequence?, connectedDevice: MediaDeviceData?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = routingSessionName?.toString()
+                str2 = connectedDevice?.name?.toString()
+            },
+            { "Remote device: $str1 or $str2 or unknown" }
+        )
+    }
+
+    fun logDeviceName(
+        device: MediaDevice?,
+        controller: MediaController?,
+        routingSessionName: CharSequence?,
+        selectedRouteName: CharSequence?
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = "device $device, controller: $controller"
+                str2 = routingSessionName?.toString()
+                str3 = selectedRouteName?.toString()
+            },
+            { "$str1, routingSession $str2 or selected route $str3" }
+        )
+    }
+
+    companion object {
+        private const val TAG = "MediaDeviceLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index a193f7f..49b53c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -71,6 +71,7 @@
     private val localBluetoothManager: Lazy<LocalBluetoothManager?>,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
+    private val logger: MediaDeviceLogger,
 ) : MediaDataManager.Listener {
 
     private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -281,59 +282,38 @@
         }
 
         override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastStarted", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastStartFailed(reason: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
-            }
+            logger.logBroadcastEvent("onBroadcastStartFailed", reason)
         }
 
         override fun onBroadcastMetadataChanged(
             broadcastId: Int,
             metadata: BluetoothLeBroadcastMetadata
         ) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
-                        "metadata = $metadata"
-                )
-            }
+            logger.logBroadcastMetadataChanged(broadcastId, metadata.toString())
             updateCurrent()
         }
 
         override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastStopped", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastStopFailed(reason: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
-            }
+            logger.logBroadcastEvent("onBroadcastStopFailed", reason)
         }
 
         override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastUpdated", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
-                )
-            }
+            logger.logBroadcastEvent("onBroadcastUpdateFailed", reason, broadcastId)
         }
 
         override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
@@ -381,12 +361,16 @@
                                 name = context.getString(R.string.media_seamless_other_device),
                                 showBroadcastButton = false
                             )
+                    logger.logRemoteDevice(routingSession?.name, connectedDevice)
                 } else {
                     // Prefer SASS if available when playback is local.
-                    activeDevice = getSassDevice() ?: connectedDevice
+                    val sassDevice = getSassDevice()
+                    activeDevice = sassDevice ?: connectedDevice
+                    logger.logLocalDevice(sassDevice, connectedDevice)
                 }
 
                 current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA
+                logger.logNewDeviceName(current?.name?.toString())
             } else {
                 val aboutToConnect = aboutToConnectDeviceOverride
                 if (
@@ -407,9 +391,7 @@
                 val enabled = device != null && (controller == null || routingSession != null)
 
                 val name = getDeviceName(device, routingSession)
-                if (DEBUG) {
-                    Log.d(TAG, "new device name $name")
-                }
+                logger.logNewDeviceName(name)
                 current =
                     MediaDeviceData(
                         enabled,
@@ -463,14 +445,12 @@
         ): String? {
             val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) }
 
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "device is $device, controller $controller," +
-                        " routingSession ${routingSession?.name}" +
-                        " or ${selectedRoutes?.firstOrNull()?.name}"
-                )
-            }
+            logger.logDeviceName(
+                device,
+                controller,
+                routingSession?.name,
+                selectedRoutes?.firstOrNull()?.name
+            )
 
             if (controller == null) {
                 // In resume state, we don't have a controller - just use the device name
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index e4047e5..9ee59d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -80,6 +80,7 @@
             field?.disconnect()
             field = value
         }
+
     private var currentUserId: Int = context.userId
 
     @VisibleForTesting
@@ -89,7 +90,7 @@
                 if (Intent.ACTION_USER_UNLOCKED == intent.action) {
                     val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
                     if (userId == currentUserId) {
-                        loadMediaResumptionControls()
+                        backgroundExecutor.execute { loadMediaResumptionControls() }
                     }
                 }
             }
@@ -254,15 +255,15 @@
             if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 // Set null action to prevent additional attempts to connect
-                mediaDataManager.setResumeAction(key, null)
-                Log.d(TAG, "Checking for service component for " + data.packageName)
-                val pm = context.packageManager
-                val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
-                val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)
+                backgroundExecutor.execute {
+                    mediaDataManager.setResumeAction(key, null)
+                    Log.d(TAG, "Checking for service component for " + data.packageName)
+                    val pm = context.packageManager
+                    val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
+                    val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)
 
-                val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
-                if (inf != null && inf.size > 0) {
-                    backgroundExecutor.execute {
+                    val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
+                    if (inf != null && inf.size > 0) {
                         tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index cef1e69..2a9fe83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -32,6 +32,7 @@
 import androidx.core.view.GestureDetectorCompat
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
+import com.android.systemui.Flags
 import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.FalsingManager
@@ -102,9 +103,11 @@
             }
             _progress.postValue(value)
         }
+
     private val _progress = MutableLiveData<Progress>().apply { postValue(_data) }
     val progress: LiveData<Progress>
         get() = _progress
+
     private var controller: MediaController? = null
         set(value) {
             if (field?.sessionToken != value?.sessionToken) {
@@ -113,6 +116,7 @@
                 field = value
             }
         }
+
     private var playbackState: PlaybackState? = null
     private var callback =
         object : MediaController.Callback() {
@@ -128,6 +132,15 @@
             override fun onSessionDestroyed() {
                 clearController()
             }
+
+            override fun onMetadataChanged(metadata: MediaMetadata?) {
+                if (!Flags.mediaControlsPostsOptimization()) return
+
+                val (enabled, duration) = getEnabledStateAndDuration(metadata)
+                if (_data.duration != duration) {
+                    _data = _data.copy(enabled = enabled, duration = duration)
+                }
+            }
         }
     private var cancel: Runnable? = null
 
@@ -233,22 +246,13 @@
     fun updateController(mediaController: MediaController?) {
         controller = mediaController
         playbackState = controller?.playbackState
-        val mediaMetadata = controller?.metadata
+        val (enabled, duration) = getEnabledStateAndDuration(controller?.metadata)
         val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
         val position = playbackState?.position?.toInt()
-        val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
         val playing =
             NotificationMediaManager.isPlayingState(
                 playbackState?.state ?: PlaybackState.STATE_NONE
             )
-        val enabled =
-            if (
-                playbackState == null ||
-                    playbackState?.getState() == PlaybackState.STATE_NONE ||
-                    (duration <= 0)
-            )
-                false
-            else true
         _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
         checkIfPollingNeeded()
     }
@@ -368,6 +372,16 @@
         }
     }
 
+    /** returns a pair of whether seekbar is enabled and the duration of media. */
+    private fun getEnabledStateAndDuration(metadata: MediaMetadata?): Pair<Boolean, Int> {
+        val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
+        val enabled =
+            !(playbackState == null ||
+                playbackState?.state == PlaybackState.STATE_NONE ||
+                (duration <= 0))
+        return Pair(enabled, duration)
+    }
+
     /**
      * This method specifies if user made a bad seekbar grab or not. If the vertical distance from
      * first touch on seekbar is more than the horizontal distance, this means that the seekbar grab
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 18c6f53..4251b81 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -307,7 +307,7 @@
 
     private void setUpDialog(AlertDialog dialog) {
         SystemUIDialog.registerDismissListener(dialog);
-        SystemUIDialog.applyFlags(dialog);
+        SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false);
         SystemUIDialog.setDialogSize(dialog);
 
         dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index e44069f..b3c697e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -425,6 +425,18 @@
         }
     }
 
+    private void appTransitionPending(boolean pending) {
+        if (mOverviewProxyService.getProxy() == null) {
+            return;
+        }
+
+        try {
+            mOverviewProxyService.getProxy().appTransitionPending(pending);
+        } catch (RemoteException e) {
+            Log.e(TAG, "appTransitionPending() failed, pending: " + pending, e);
+        }
+    }
+
     @Override
     public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
             @BackDispositionMode int backDisposition, boolean showImeSwitcher) {
@@ -533,6 +545,21 @@
         }
     }
 
+    @Override
+    public void appTransitionPending(int displayId, boolean forced) {
+        appTransitionPending(true);
+    }
+
+    @Override
+    public void appTransitionCancelled(int displayId) {
+        appTransitionPending(false);
+    }
+
+    @Override
+    public void appTransitionFinished(int displayId) {
+        appTransitionPending(false);
+    }
+
     private void clearTransient() {
         if (mTaskbarTransientShowing) {
             mTaskbarTransientShowing = false;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index e8c90c1..c70a523 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -34,7 +34,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
-import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
@@ -60,7 +59,6 @@
 import android.app.IActivityTaskManager;
 import android.app.StatusBarManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -105,7 +103,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -1625,11 +1622,9 @@
     }
 
     private boolean onAccessibilityLongClick(View v) {
-        final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
-        intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
+        final Display display = v.getDisplay();
+        mAccessibilityManager.notifyAccessibilityButtonLongClicked(
+                display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3a8fe71..ef1f834 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.QRCodeScannerTile
 import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
@@ -51,7 +52,7 @@
     @IntoMap
     @StringKey(QR_CODE_SCANNER_TILE_SPEC)
     fun provideQrCodeScannerAvailabilityInteractor(
-            impl: QRCodeScannerTileDataInteractor
+        impl: QRCodeScannerTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     companion object {
@@ -69,6 +70,7 @@
                         labelRes = R.string.qr_code_scanner_title,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
             )
 
         /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index c39ff55..c2f1c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.graphics.Rect
 import android.os.Bundle
+import android.util.IndentingPrintWriter
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -41,8 +42,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layout
-import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.res.dimensionResource
@@ -59,7 +60,9 @@
 import com.android.compose.modifiers.padding
 import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.Dumpable
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -76,6 +79,10 @@
 import com.android.systemui.qs.ui.composable.ShadeBody
 import com.android.systemui.res.R
 import com.android.systemui.util.LifecycleFragment
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printSection
+import com.android.systemui.util.println
+import java.io.PrintWriter
 import java.util.function.Consumer
 import javax.inject.Inject
 import javax.inject.Named
@@ -91,9 +98,10 @@
 @Inject
 constructor(
     private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+    private val dumpManager: DumpManager,
     @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) private val qsMediaHost: MediaHost,
-) : LifecycleFragment(), QS {
+) : LifecycleFragment(), QS, Dumpable {
 
     private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
     private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
@@ -118,8 +126,24 @@
             var top by mutableStateOf(0)
             var bottom by mutableStateOf(0)
             var radius by mutableStateOf(0)
+
+            fun dump(pw: IndentingPrintWriter) {
+                pw.printSection("NotificationScrimClippingParams") {
+                    pw.println("isEnabled", isEnabled)
+                    pw.println("leftInset", "${leftInset}px")
+                    pw.println("rightInset", "${rightInset}px")
+                    pw.println("top", "${top}px")
+                    pw.println("bottom", "${bottom}px")
+                    pw.println("radius", "${radius}px")
+                }
+            }
         }
 
+    override fun onStart() {
+        super.onStart()
+        registerDumpable()
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -343,11 +367,11 @@
     }
 
     override fun getHeaderTop(): Int {
-        return viewModel.qqsHeaderHeight.value
+        return qqsPositionOnRoot.top
     }
 
     override fun getHeaderBottom(): Int {
-        return headerTop + qqsHeight.value
+        return qqsPositionOnRoot.bottom
     }
 
     override fun getHeaderLeft(): Int {
@@ -358,7 +382,7 @@
         outBounds.set(qqsPositionOnRoot)
         view?.getBoundsOnScreen(composeViewPositionOnScreen)
             ?: run { composeViewPositionOnScreen.setEmpty() }
-        qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top)
+        outBounds.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top)
     }
 
     override fun isHeaderShown(): Boolean {
@@ -404,37 +428,29 @@
             onDispose { qqsVisible.value = false }
         }
         Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
-            Box(modifier = Modifier.fillMaxWidth()) {
+            Box(
+                modifier =
+                    Modifier.fillMaxWidth()
+                        .onPlaced { coordinates ->
+                            val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
+                            qqsPositionOnRoot.set(
+                                leftFromRoot,
+                                topFromRoot,
+                                leftFromRoot + coordinates.size.width,
+                                topFromRoot + coordinates.size.height,
+                            )
+                        }
+                        .onSizeChanged { size -> qqsHeight.value = size.height }
+                        .padding(top = { qqsPadding })
+            ) {
                 val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
                 if (qsEnabled) {
                     QuickQuickSettings(
                         viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
                         modifier =
-                            Modifier.onGloballyPositioned { coordinates ->
-                                    val (leftFromRoot, topFromRoot) =
-                                        coordinates.positionInRoot().round()
-                                    val (width, height) = coordinates.size
-                                    qqsPositionOnRoot.set(
-                                        leftFromRoot,
-                                        topFromRoot,
-                                        leftFromRoot + width,
-                                        topFromRoot + height
-                                    )
-                                }
-                                .layout { measurable, constraints ->
-                                    val placeable = measurable.measure(constraints)
-                                    qqsHeight.value = placeable.height
-
-                                    layout(placeable.width, placeable.height) {
-                                        placeable.place(0, 0)
-                                    }
-                                }
-                                .padding(top = { qqsPadding })
-                                .collapseExpandSemanticAction(
-                                    stringResource(
-                                        id = R.string.accessibility_quick_settings_expand
-                                    )
-                                )
+                            Modifier.collapseExpandSemanticAction(
+                                stringResource(id = R.string.accessibility_quick_settings_expand)
+                            )
                     )
                 }
             }
@@ -486,6 +502,44 @@
             }
         } ?: this
     }
+
+    private fun registerDumpable() {
+        val instanceId = instanceProvider.getNextId()
+        // Add an instanceId because the system may have more than 1 of these when re-inflating and
+        // DumpManager doesn't like repeated identifiers. Also, put it first because DumpHandler
+        // matches by end.
+        val stringId = "$instanceId-QSFragmentCompose"
+        lifecycleScope.launch {
+            lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
+                try {
+                    dumpManager.registerNormalDumpable(stringId, this@QSFragmentCompose)
+                    awaitCancellation()
+                } finally {
+                    dumpManager.unregisterDumpable(stringId)
+                }
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.asIndenting().run {
+            notificationScrimClippingParams.dump(this)
+            printSection("QQS positioning") {
+                println("qqsHeight", "${headerHeight}px")
+                println("qqsTop", "${headerTop}px")
+                println("qqsBottom", "${headerBottom}px")
+                println("qqsLeft", "${headerLeft}px")
+                println("qqsPositionOnRoot", qqsPositionOnRoot)
+                val rect = Rect()
+                getHeaderBoundsOnScreen(rect)
+                println("qqsPositionOnScreen", rect)
+            }
+            println("QQS visible", qqsVisible.value)
+            if (::viewModel.isInitialized) {
+                printSection("View Model") { viewModel.dump(this@run, args) }
+            }
+        }
+    }
 }
 
 private fun View.setBackPressedDispatcher() {
@@ -526,3 +580,12 @@
         }
     }
 }
+
+private val instanceProvider =
+    object {
+        private var currentId = 0
+
+        fun getNextId(): Int {
+            return currentId++
+        }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 16133f4..7ab11d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.Dumpable
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -34,10 +35,14 @@
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printSection
+import com.android.systemui.util.println
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.io.PrintWriter
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -62,7 +67,7 @@
     private val configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
-) {
+) : Dumpable {
     val footerActionsViewModel =
         footerActionsViewModelFactory.create(lifecycleScope).also {
             lifecycleScope.launch { footerActionsController.init() }
@@ -228,6 +233,30 @@
      */
     var collapseExpandAccessibilityAction: Runnable? = null
 
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.asIndenting().run {
+            printSection("Quick Settings state") {
+                println("isQSExpanded", isQSExpanded)
+                println("isQSVisible", isQSVisible)
+                println("isQSEnabled", qsEnabled.value)
+                println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value)
+            }
+            printSection("Expansion state") {
+                println("qsExpansion", qsExpansionValue)
+                println("panelExpansionFraction", panelExpansionFractionValue)
+                println("squishinessFraction", squishinessFractionValue)
+                println("expansionState", expansionState.value)
+            }
+            printSection("Shade state") {
+                println("stackOverscrolling", stackScrollerOverscrollingValue)
+                println("statusBarState", StatusBarState.toString(statusBarState.value))
+                println("isSmallScreen", isSmallScreenValue)
+                println("heightOverride", "${heightOverrideValue}px")
+                println("qqsHeaderHeight", "${qqsHeaderHeight.value}px")
+            }
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt
index 28c1fbf..2f054b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt
@@ -24,10 +24,10 @@
 import com.android.systemui.qs.panels.shared.model.EditTileData
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 
 @SysUISingleton
@@ -60,6 +60,7 @@
                             Icon.Loaded(icon, ContentDescription.Loaded(label.toString())),
                             Text.Loaded(label.toString()),
                             Text.Loaded(appName.toString()),
+                            TileCategory.PROVIDED_BY_APP,
                         )
                     } else {
                         null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt
index 3b29422..a2cee3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.panels.data.repository.StockTilesRepository
 import com.android.systemui.qs.panels.domain.model.EditTilesModel
 import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import javax.inject.Inject
 
@@ -53,6 +54,7 @@
                         ),
                         Text.Resource(config.uiConfig.labelRes),
                         null,
+                        category = config.category,
                     )
                 } else {
                     EditTileData(
@@ -62,7 +64,8 @@
                             ContentDescription.Loaded(it.spec)
                         ),
                         Text.Loaded(it.spec),
-                        null
+                        null,
+                        category = TileCategory.UNKNOWN,
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt
index 8b70bb9..b153ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 
 data class EditTileData(
     val tileSpec: TileSpec,
     val icon: Icon,
     val label: Text,
     val appName: Text?,
+    val category: TileCategory,
 ) {
     init {
         check(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 24af09d..93037d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -34,6 +34,7 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.background
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.border
 import androidx.compose.foundation.combinedClickable
@@ -95,6 +96,8 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastMap
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
 import com.android.compose.modifiers.background
@@ -115,6 +118,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.groupAndSort
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.res.R
 import java.util.function.Supplier
@@ -472,31 +476,39 @@
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
 ) {
-    // Available tiles aren't visible during drag and drop, so the row isn't needed
-    val (otherTilesStock, otherTilesCustom) =
-        tiles.map { TileGridCell(it, 0) }.partition { it.tile.appName == null }
     val availableTileHeight = tileHeight(true)
     val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
 
+    // Available tiles aren't visible during drag and drop, so the row isn't needed
+    val groupedTiles =
+        remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
+            groupAndSort(tiles.fastMap { TileGridCell(it, 0) })
+        }
+    val labelColors = TileDefaults.inactiveTileColors()
     // Available tiles
     TileLazyGrid(
         modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
         columns = GridCells.Fixed(columns)
     ) {
-        editTiles(
-            otherTilesStock,
-            ClickAction.ADD,
-            onClick,
-            dragAndDropState = dragAndDropState,
-            showLabels = true,
-        )
-        editTiles(
-            otherTilesCustom,
-            ClickAction.ADD,
-            onClick,
-            dragAndDropState = dragAndDropState,
-            showLabels = true,
-        )
+        groupedTiles.forEach { category, tiles ->
+            stickyHeader {
+                Text(
+                    text = category.label.load() ?: "",
+                    fontSize = 20.sp,
+                    color = labelColors.label,
+                    modifier =
+                        Modifier.background(Color.Black)
+                            .padding(start = 16.dp, bottom = 8.dp, top = 8.dp)
+                )
+            }
+            editTiles(
+                tiles,
+                ClickAction.ADD,
+                onClick,
+                dragAndDropState = dragAndDropState,
+                showLabels = true,
+            )
+        }
     }
 }
 
@@ -618,7 +630,7 @@
     showLabels: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
+    val label = tileViewModel.label.text
     val colors = TileDefaults.inactiveTileColors()
 
     TileContainer(
@@ -638,7 +650,7 @@
         } else {
             LargeTileContent(
                 label = label,
-                secondaryLabel = tileViewModel.appName?.load(),
+                secondaryLabel = tileViewModel.appName?.text,
                 icon = tileViewModel.icon,
                 colors = colors,
                 iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index 8ca8de7..08ee856 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.shared.model.CategoryAndName
 
 /** Represents an item from a grid associated with a row and a span */
 interface GridCell {
@@ -38,7 +39,7 @@
     override val row: Int,
     override val width: Int,
     override val span: GridItemSpan = GridItemSpan(width)
-) : GridCell, SizedTile<EditTileViewModel> {
+) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
     val key: String = "${tile.tileSpec.spec}-$row"
 
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 42715be..4a8aa83e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import android.content.Context
+import androidx.compose.ui.util.fastMap
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor
@@ -27,6 +30,7 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
@@ -35,6 +39,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
@@ -49,6 +54,8 @@
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
     private val minTilesInteractor: MinimumTilesInteractor,
+    private val configurationInteractor: ConfigurationInteractor,
+    @Application private val applicationContext: Context,
     @Named("Default") private val defaultGridLayout: GridLayout,
     @Application private val applicationScope: CoroutineScope,
     gridLayoutTypeInteractor: GridLayoutTypeInteractor,
@@ -99,38 +106,45 @@
                             .map { it.tileSpec }
                             .minus(currentTilesInteractor.currentTilesSpecs.toSet())
                     )
-                currentTilesInteractor.currentTiles.map { tiles ->
-                    val currentSpecs = tiles.map { it.spec }
-                    val canRemoveTiles = currentSpecs.size > minimumTiles
-                    val allTiles = editTilesData.stockTiles + editTilesData.customTiles
-                    val allTilesMap = allTiles.associate { it.tileSpec to it }
-                    val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull()
-                    val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
+                currentTilesInteractor.currentTiles
+                    .map { tiles ->
+                        val currentSpecs = tiles.map { it.spec }
+                        val canRemoveTiles = currentSpecs.size > minimumTiles
+                        val allTiles = editTilesData.stockTiles + editTilesData.customTiles
+                        val allTilesMap = allTiles.associate { it.tileSpec to it }
+                        val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull()
+                        val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
 
-                    (currentTiles + nonCurrentTiles)
-                        .filterNot { it.tileSpec in unavailable }
-                        .map {
-                            val current = it.tileSpec in currentSpecs
-                            val availableActions = buildSet {
-                                if (current) {
-                                    add(AvailableEditActions.MOVE)
-                                    if (canRemoveTiles) {
-                                        add(AvailableEditActions.REMOVE)
+                        (currentTiles + nonCurrentTiles)
+                            .filterNot { it.tileSpec in unavailable }
+                            .map {
+                                val current = it.tileSpec in currentSpecs
+                                val availableActions = buildSet {
+                                    if (current) {
+                                        add(AvailableEditActions.MOVE)
+                                        if (canRemoveTiles) {
+                                            add(AvailableEditActions.REMOVE)
+                                        }
+                                    } else {
+                                        add(AvailableEditActions.ADD)
                                     }
-                                } else {
-                                    add(AvailableEditActions.ADD)
                                 }
+                                UnloadedEditTileViewModel(
+                                    it.tileSpec,
+                                    it.icon,
+                                    it.label,
+                                    it.appName,
+                                    current,
+                                    availableActions,
+                                    it.category,
+                                )
                             }
-                            EditTileViewModel(
-                                it.tileSpec,
-                                it.icon,
-                                it.label,
-                                it.appName,
-                                current,
-                                availableActions
-                            )
-                        }
-                }
+                    }
+                    .combine(configurationInteractor.onAnyConfigurationChange.emitOnStart()) {
+                        tiles,
+                        _ ->
+                        tiles.fastMap { it.load(applicationContext) }
+                    }
             } else {
                 emptyFlow()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
index a4c8638..ee12736f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
@@ -16,9 +16,15 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import android.content.Context
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.AnnotatedString
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.ui.compose.toAnnotatedString
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.CategoryAndName
+import com.android.systemui.qs.shared.model.TileCategory
 
 /**
  * View model for each tile that is available to be added/removed/moved in Edit mode.
@@ -26,14 +32,41 @@
  * [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in
  * Quick Settings.
  */
-data class EditTileViewModel(
+data class UnloadedEditTileViewModel(
     val tileSpec: TileSpec,
     val icon: Icon,
     val label: Text,
     val appName: Text?,
     val isCurrent: Boolean,
     val availableEditActions: Set<AvailableEditActions>,
-)
+    val category: TileCategory,
+) {
+    fun load(context: Context): EditTileViewModel {
+        return EditTileViewModel(
+            tileSpec,
+            icon,
+            label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec),
+            appName?.toAnnotatedString(context),
+            isCurrent,
+            availableEditActions,
+            category,
+        )
+    }
+}
+
+@Immutable
+data class EditTileViewModel(
+    val tileSpec: TileSpec,
+    val icon: Icon,
+    val label: AnnotatedString,
+    val appName: AnnotatedString?,
+    val isCurrent: Boolean,
+    val availableEditActions: Set<AvailableEditActions>,
+    override val category: TileCategory,
+) : CategoryAndName {
+    override val name
+        get() = label.text
+}
 
 enum class AvailableEditActions {
     ADD,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
index c56ca8c..d50374b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -15,9 +15,7 @@
  */
 package com.android.systemui.qs.pipeline.dagger
 
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
 import javax.inject.Qualifier
 
 /** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
-@Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt
new file mode 100644
index 0000000..59cb7d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.shared.model
+
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.res.R
+
+/** Categories for tiles. This can be used to sort tiles in edit mode. */
+enum class TileCategory(val label: Text) {
+    CONNECTIVITY(Text.Resource(R.string.qs_edit_mode_category_connectivity)),
+    UTILITIES(Text.Resource(R.string.qs_edit_mode_category_utilities)),
+    DISPLAY(Text.Resource(R.string.qs_edit_mode_category_display)),
+    PRIVACY(Text.Resource(R.string.qs_edit_mode_category_privacy)),
+    ACCESSIBILITY(Text.Resource(R.string.qs_edit_mode_category_accessibility)),
+    PROVIDED_BY_APP(Text.Resource(R.string.qs_edit_mode_category_providedByApps)),
+    UNKNOWN(Text.Resource(R.string.qs_edit_mode_category_unknown)),
+}
+
+interface CategoryAndName {
+    val category: TileCategory
+    val name: String
+}
+
+/**
+ * Groups the elements of the list by [CategoryAndName.category] (with the keys sorted in the
+ * natural order of [TileCategory]), and sorts the elements of each group based on the
+ * [CategoryAndName.name].
+ */
+fun <T : CategoryAndName> groupAndSort(list: List<T>): Map<TileCategory, List<T>> {
+    val groupedByCategory = list.groupBy { it.category }.toSortedMap()
+    return groupedByCategory.mapValues { it.value.sortedBy { it.name } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index cdcefdb..3a9cb17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.StringRes
 import com.android.internal.logging.InstanceId
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 
 data class QSTileConfig
 @JvmOverloads
@@ -28,6 +29,7 @@
     val tileSpec: TileSpec,
     val uiConfig: QSTileUIConfig,
     val instanceId: InstanceId,
+    val category: TileCategory,
     val metricsSpec: String = tileSpec.spec,
     val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
     val autoRemoveOnUnavailable: Boolean = true,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
index 0609e79..4dbddd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import javax.inject.Inject
 
 interface QSTileConfigProvider {
@@ -73,6 +74,7 @@
                     spec,
                     QSTileUIConfig.Empty,
                     qsEventLogger.getNewInstanceId(),
+                    category = TileCategory.PROVIDED_BY_APP,
                 )
             is TileSpec.Invalid ->
                 throw IllegalArgumentException("TileSpec.Invalid doesn't support configs")
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 000781a..a402a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,7 +25,6 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
-import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -76,7 +75,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -404,23 +402,16 @@
         @Override
         public void notifyAccessibilityButtonClicked(int displayId) {
             verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonClicked", () ->
-                    AccessibilityManager.getInstance(mContext)
-                            .notifyAccessibilityButtonClicked(displayId));
+                    AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
+                            displayId));
         }
 
         @Override
         public void notifyAccessibilityButtonLongClicked() {
-            verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonLongClicked",
-                    () -> {
-                        final Intent intent =
-                                new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
-                        final String chooserClassName = AccessibilityButtonChooserActivity
-                                .class.getName();
-                        intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-                        intent.addFlags(
-                                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-                        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
-                    });
+            verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonLongClicked", () ->
+                    AccessibilityManager.getInstance(mContext)
+                            .notifyAccessibilityButtonLongClicked(
+                                    mDisplayTracker.getDefaultDisplayId()));
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
index 907b92c..c092c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
@@ -61,6 +62,7 @@
                         labelRes = R.string.qs_record_issue_label
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
             )
 
         /** Inject FlashlightTile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
index 0589e6c..c9712fc 100644
--- a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.camera.CameraRotationModule
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
 import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor
@@ -42,8 +43,9 @@
     @IntoMap
     @StringKey(ROTATION_TILE_SPEC)
     fun provideRotationAvailabilityInteractor(
-            impl: RotationLockTileDataInteractor
+        impl: RotationLockTileDataInteractor
     ): QSTileAvailabilityInteractor
+
     companion object {
         private const val ROTATION_TILE_SPEC = "rotation"
 
@@ -60,6 +62,7 @@
                         labelRes = R.string.quick_settings_rotation_unlocked_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         /** Inject Rotation tile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index 738b184..e477efe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -34,6 +35,7 @@
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -45,6 +47,7 @@
 import kotlinx.coroutines.launch
 
 /** Business logic about the visibility of various parts of the window root view. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class WindowRootViewVisibilityInteractor
 @Inject
@@ -80,9 +83,9 @@
                         is ObservableTransitionState.Idle ->
                             flowOf(
                                 state.currentScene == Scenes.Shade ||
-                                    state.currentScene == Scenes.NotificationsShade ||
-                                    state.currentScene == Scenes.QuickSettingsShade ||
-                                    state.currentScene == Scenes.Lockscreen
+                                    state.currentScene == Scenes.Lockscreen ||
+                                    Overlays.NotificationsShade in state.currentOverlays ||
+                                    Overlays.QuickSettingsShade in state.currentOverlays
                             )
                         is ObservableTransitionState.Transition ->
                             if (
@@ -94,12 +97,12 @@
                             } else {
                                 flowOf(
                                     state.toContent == Scenes.Shade ||
-                                        state.toContent == Scenes.NotificationsShade ||
-                                        state.toContent == Scenes.QuickSettingsShade ||
+                                        state.toContent == Overlays.NotificationsShade ||
+                                        state.toContent == Overlays.QuickSettingsShade ||
                                         state.toContent == Scenes.Lockscreen ||
                                         state.fromContent == Scenes.Shade ||
-                                        state.fromContent == Scenes.NotificationsShade ||
-                                        state.fromContent == Scenes.QuickSettingsShade ||
+                                        state.fromContent == Overlays.NotificationsShade ||
+                                        state.fromContent == Overlays.QuickSettingsShade ||
                                         state.fromContent == Scenes.Lockscreen
                                 )
                             }
@@ -115,10 +118,9 @@
      * false if the device is asleep.
      */
     val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
-        combine(
-                isLockscreenOrShadeVisible,
-                powerInteractor.isAwake,
-            ) { isKeyguardAodOrShadeVisible, isAwake ->
+        combine(isLockscreenOrShadeVisible, powerInteractor.isAwake) {
+                isKeyguardAodOrShadeVisible,
+                isAwake ->
                 isKeyguardAodOrShadeVisible && isAwake
             }
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
index 99e554e..a313273 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.Binds
 import dagger.Module
@@ -38,17 +38,17 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    shadeInteractor: ShadeInteractor,
+    shadeModeInteractor: ShadeModeInteractor,
 ) : SceneResolver {
     override val targetFamily: SceneKey = SceneFamilies.NotifShade
 
     override val resolvedScene: StateFlow<SceneKey> =
-        shadeInteractor.shadeMode
+        shadeModeInteractor.shadeMode
             .map(::notifShadeScene)
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = notifShadeScene(shadeInteractor.shadeMode.value),
+                initialValue = notifShadeScene(shadeModeInteractor.shadeMode.value),
             )
 
     override fun includesScene(scene: SceneKey): Boolean = scene in notifShadeScenes
@@ -61,11 +61,7 @@
         }
 
     companion object {
-        val notifShadeScenes =
-            setOf(
-                Scenes.NotificationsShade,
-                Scenes.Shade,
-            )
+        val notifShadeScenes = setOf(Scenes.NotificationsShade, Scenes.Shade)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
index 2962a3e..923e712a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.Binds
 import dagger.Module
@@ -38,17 +38,17 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    shadeInteractor: ShadeInteractor,
+    shadeModeInteractor: ShadeModeInteractor,
 ) : SceneResolver {
     override val targetFamily: SceneKey = SceneFamilies.QuickSettings
 
     override val resolvedScene: StateFlow<SceneKey> =
-        shadeInteractor.shadeMode
+        shadeModeInteractor.shadeMode
             .map(::quickSettingsScene)
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = quickSettingsScene(shadeInteractor.shadeMode.value),
+                initialValue = quickSettingsScene(shadeModeInteractor.shadeMode.value),
             )
 
     override fun includesScene(scene: SceneKey): Boolean = scene in quickSettingsScenes
@@ -62,11 +62,7 @@
 
     companion object {
         val quickSettingsScenes =
-            setOf(
-                Scenes.QuickSettings,
-                Scenes.QuickSettingsShade,
-                Scenes.Shade,
-            )
+            setOf(Scenes.QuickSettings, Scenes.QuickSettingsShade, Scenes.Shade)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index d1629c7..e352bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -19,7 +19,9 @@
 package com.android.systemui.scene.domain.startable
 
 import androidx.annotation.VisibleForTesting
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -33,6 +35,7 @@
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
 import com.android.systemui.statusbar.phone.DozeServiceHost
@@ -74,6 +77,7 @@
                 deviceEntryInteractor.isDeviceEntered,
                 occlusionInteractor.invisibleDueToOcclusion,
                 sceneInteractor.currentScene,
+                sceneInteractor.currentOverlays,
                 sceneInteractor.transitionState,
                 keyguardInteractor.isDozing,
                 keyguardInteractor.isDreaming,
@@ -95,34 +99,29 @@
                 val isDeviceEntered = flowValues[0] as Boolean
                 val isOccluded = flowValues[1] as Boolean
                 val currentScene = flowValues[2] as SceneKey
-                val transitionState = flowValues[3] as ObservableTransitionState
-                val isDozing = flowValues[4] as Boolean
-                val isDreaming = flowValues[5] as Boolean
-                val biometricUnlockState = flowValues[6] as BiometricUnlockModel
-                val isBrightnessMirrorVisible = flowValues[7] as Boolean
-                val isPulsing = flowValues[8] as Boolean
-                val hasPendingScreenOffCallback = flowValues[9] as Boolean
+                val currentOverlays = flowValues[3] as Set<OverlayKey>
+                val transitionState = flowValues[4] as ObservableTransitionState
+                val isDozing = flowValues[5] as Boolean
+                val isDreaming = flowValues[6] as Boolean
+                val biometricUnlockState = flowValues[7] as BiometricUnlockModel
+                val isBrightnessMirrorVisible = flowValues[8] as Boolean
+                val isPulsing = flowValues[9] as Boolean
+                val hasPendingScreenOffCallback = flowValues[10] as Boolean
 
                 // This is true when the lockscreen scene is either the current scene or somewhere
-                // in the
-                // navigation back stack of scenes.
+                // in the navigation back stack of scenes.
                 val isOnKeyguard = !isDeviceEntered
                 val isCurrentSceneBouncer = currentScene == Scenes.Bouncer
                 // This is true when moving away from one of the keyguard scenes to the gone scene.
-                // It
-                // happens only when unlocking or when dismissing a dismissible lockscreen.
+                // It happens only when unlocking or when dismissing a dismissible lockscreen.
                 val isTransitioningAwayFromKeyguard =
                     transitionState is ObservableTransitionState.Transition.ChangeScene &&
                         transitionState.fromScene.isKeyguard() &&
                         transitionState.toScene == Scenes.Gone
 
-                // This is true when any of the shade scenes is the current scene.
-                val isCurrentSceneShade = currentScene.isShade()
-                // This is true when moving into one of the shade scenes when a non-shade scene.
-                val isTransitioningToShade =
-                    transitionState is ObservableTransitionState.Transition.ChangeScene &&
-                        !transitionState.fromScene.isShade() &&
-                        transitionState.toScene.isShade()
+                // This is true when any of the shade scenes or overlays is the current content.
+                val isCurrentContentShade =
+                    currentScene.isShade() || currentOverlays.any { it.isShade() }
 
                 // This is true after completing a transition to communal.
                 val isIdleOnCommunal = transitionState.isIdle(Scenes.Communal)
@@ -137,10 +136,10 @@
 
                 if (alternateBouncerInteractor.isVisibleState()) {
                     // This will cancel the keyguardFadingAway animation if it is running. We need
-                    // to do
-                    // this as otherwise it can remain pending and leave keyguard in a weird state.
+                    // to do this as otherwise it can remain pending and leave keyguard in a weird
+                    // state.
                     onKeyguardFadedAway(isTransitioningAwayFromKeyguard)
-                    if (!isTransitioningToShade) {
+                    if (!transitionState.isTransitioningToShade()) {
                         // Safeguard which prevents the scrim from being stuck in the wrong
                         // state
                         Model(scrimState = ScrimState.KEYGUARD, unlocking = unlocking)
@@ -163,7 +162,7 @@
                     )
                 } else if (isBrightnessMirrorVisible) {
                     Model(scrimState = ScrimState.BRIGHTNESS_MIRROR, unlocking = unlocking)
-                } else if (isCurrentSceneShade && !isDeviceEntered) {
+                } else if (isCurrentContentShade && !isDeviceEntered) {
                     Model(scrimState = ScrimState.SHADE_LOCKED, unlocking = unlocking)
                 } else if (isPulsing) {
                     Model(scrimState = ScrimState.PULSING, unlocking = unlocking)
@@ -171,8 +170,8 @@
                     Model(scrimState = ScrimState.OFF, unlocking = unlocking)
                 } else if (isDozing && !unlocking) {
                     // This will cancel the keyguardFadingAway animation if it is running. We need
-                    // to do
-                    // this as otherwise it can remain pending and leave keyguard in a weird state.
+                    // to do this as otherwise it can remain pending and leave keyguard in a weird
+                    // state.
                     onKeyguardFadedAway(isTransitioningAwayFromKeyguard)
                     Model(scrimState = ScrimState.AOD, unlocking = false)
                 } else if (isIdleOnCommunal) {
@@ -222,15 +221,24 @@
         return this == Scenes.Lockscreen || this == Scenes.Bouncer
     }
 
-    private fun SceneKey.isShade(): Boolean {
+    private fun ContentKey.isShade(): Boolean {
         return this == Scenes.Shade ||
             this == Scenes.QuickSettings ||
-            this == Scenes.NotificationsShade ||
-            this == Scenes.QuickSettingsShade
+            this == Overlays.NotificationsShade ||
+            this == Overlays.QuickSettingsShade
     }
 
-    private data class Model(
-        val scrimState: ScrimState,
-        val unlocking: Boolean,
-    )
+    private fun ObservableTransitionState.isTransitioningToShade(): Boolean {
+        return when (this) {
+            is ObservableTransitionState.Idle -> false
+            is ObservableTransitionState.Transition.ChangeScene ->
+                !fromScene.isShade() && toScene.isShade()
+            is ObservableTransitionState.Transition.ReplaceOverlay ->
+                !fromOverlay.isShade() && toOverlay.isShade()
+            is ObservableTransitionState.Transition.ShowOrHideOverlay ->
+                !fromContent.isShade() && toContent.isShade()
+        }
+    }
+
+    private data class Model(val scrimState: ScrimState, val unlocking: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
index a830e1b..9a9c576 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.ScreenRecordTile
 import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
@@ -74,6 +75,7 @@
                         labelRes = R.string.quick_settings_screen_record_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         /** Inject ScreenRecord Tile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index a77375c..f69b0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -19,7 +19,6 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
-import static com.android.systemui.Flags.screenshotSaveImageExporter;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -133,7 +132,6 @@
     private final MessageContainerController mMessageContainerController;
     private final AnnouncementResolver mAnnouncementResolver;
     private Bitmap mScreenBitmap;
-    private SaveImageInBackgroundTask mSaveInBgTask;
     private boolean mScreenshotTakenInPortrait;
     private boolean mAttachRequested;
     private boolean mDetachRequested;
@@ -393,10 +391,6 @@
     // Any cleanup needed when the service is being destroyed.
     @Override
     public void onDestroy() {
-        if (mSaveInBgTask != null) {
-            // just log success/failure for the pre-existing screenshot
-            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
-        }
         removeWindow();
         releaseMediaPlayer();
         releaseContext();
@@ -598,36 +592,12 @@
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSoundIfNeeded();
 
-        if (screenshotSaveImageExporter()) {
-            saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
-                if (result.uri != null) {
-                    mScreenshotHandler.post(() -> Toast.makeText(mContext,
-                            R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
-                }
-            });
-        } else {
-            saveScreenshotInWorkerThread(
-                    screenshot.getUserHandle(),
-                    /* onComplete */ finisher,
-                    /* actionsReadyListener */ imageData -> {
-                        if (DEBUG_CALLBACK) {
-                            Log.d(TAG,
-                                    "returning URI to finisher (Consumer<URI>): " + imageData.uri);
-                        }
-                        finisher.accept(imageData.uri);
-                        if (imageData.uri == null) {
-                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
-                                    mPackageName);
-                            mNotificationsController.notifyScreenshotError(
-                                    R.string.screenshot_failed_to_save_text);
-                        } else {
-                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
-                            mScreenshotHandler.post(() -> Toast.makeText(mContext,
-                                    R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
-                        }
-                    },
-                    null);
-        }
+        saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+            if (result.uri != null) {
+                mScreenshotHandler.post(() -> Toast.makeText(mContext,
+                        R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+            }
+        });
     }
 
     /**
@@ -700,35 +670,6 @@
     }
 
     /**
-     * Creates a new worker thread and saves the screenshot to the media store.
-     */
-    private void saveScreenshotInWorkerThread(
-            UserHandle owner,
-            @NonNull Consumer<Uri> finisher,
-            @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
-            @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
-                    quickShareActionsReadyListener) {
-        SaveImageInBackgroundTask.SaveImageInBackgroundData
-                data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
-        data.image = mScreenBitmap;
-        data.finisher = finisher;
-        data.mActionsReadyListener = actionsReadyListener;
-        data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
-        data.owner = owner;
-        data.displayId = mDisplay.getDisplayId();
-
-        if (mSaveInBgTask != null) {
-            // just log success/failure for the pre-existing screenshot
-            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
-        }
-
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
-                mScreenshotSmartActions, data,
-                mScreenshotNotificationSmartActionsProvider);
-        mSaveInBgTask.execute();
-    }
-
-    /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
     private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
@@ -745,13 +686,6 @@
         }
     }
 
-    /**
-     * Logs success/failure of the screenshot saving task, and shows an error if it failed.
-     */
-    private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
-        logScreenshotResultStatus(imageData.uri, imageData.owner);
-    }
-
     private boolean isUserSetupComplete(UserHandle owner) {
         return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
                 .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
deleted file mode 100644
index 9bc3bd8..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
-import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.flags.FeatureFlags;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * An AsyncTask that saves an image to the media store in the background.
- */
-class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
-    private static final String TAG = logTag(SaveImageInBackgroundTask.class);
-
-    private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
-    private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
-
-    /**
-     * POD used in the AsyncTask which saves an image in the background.
-     */
-    static class SaveImageInBackgroundData {
-        public Bitmap image;
-        public Consumer<Uri> finisher;
-        public ActionsReadyListener mActionsReadyListener;
-        public QuickShareActionReadyListener mQuickShareActionsReadyListener;
-        public UserHandle owner;
-        public int displayId;
-
-        void clearImage() {
-            image = null;
-        }
-    }
-
-    /**
-     * Structure returned by the SaveImageInBackgroundTask
-     */
-    public static class SavedImageData {
-        public Uri uri;
-        public List<Notification.Action> smartActions;
-        public Notification.Action quickShareAction;
-        public UserHandle owner;
-        public String subject;  // Title for sharing
-        public Long imageTime; // Time at which screenshot was saved
-
-        /**
-         * Used to reset the return data on error
-         */
-        public void reset() {
-            uri = null;
-            smartActions = null;
-            quickShareAction = null;
-            subject = null;
-            imageTime = null;
-        }
-    }
-
-    /**
-     * Structure returned by the QueryQuickShareInBackgroundTask
-     */
-    static class QuickShareData {
-        public Notification.Action quickShareAction;
-
-        /**
-         * Used to reset the return data on error
-         */
-        public void reset() {
-            quickShareAction = null;
-        }
-    }
-
-    interface ActionsReadyListener {
-        void onActionsReady(SavedImageData imageData);
-    }
-
-    interface QuickShareActionReadyListener {
-        void onActionsReady(QuickShareData quickShareData);
-    }
-
-    private final Context mContext;
-    private FeatureFlags mFlags;
-    private final ScreenshotSmartActions mScreenshotSmartActions;
-    private final SaveImageInBackgroundData mParams;
-    private final SavedImageData mImageData;
-    private final QuickShareData mQuickShareData;
-
-    private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
-    private String mScreenshotId;
-    private final Random mRandom = new Random();
-    private final ImageExporter mImageExporter;
-    private long mImageTime;
-
-    SaveImageInBackgroundTask(
-            Context context,
-            FeatureFlags flags,
-            ImageExporter exporter,
-            ScreenshotSmartActions screenshotSmartActions,
-            SaveImageInBackgroundData data,
-            ScreenshotNotificationSmartActionsProvider
-                    screenshotNotificationSmartActionsProvider
-    ) {
-        mContext = context;
-        mFlags = flags;
-        mScreenshotSmartActions = screenshotSmartActions;
-        mImageData = new SavedImageData();
-        mQuickShareData = new QuickShareData();
-        mImageExporter = exporter;
-
-        // Prepare all the output metadata
-        mParams = data;
-
-        // Initialize screenshot notification smart actions provider.
-        mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
-    }
-
-    @Override
-    protected Void doInBackground(Void... paramsUnused) {
-        if (isCancelled()) {
-            if (DEBUG_STORAGE) {
-                Log.d(TAG, "cancelled! returning null");
-            }
-            return null;
-        }
-        // TODO: move to constructor / from ScreenshotRequest
-        final UUID requestId = UUID.randomUUID();
-
-        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
-
-        Bitmap image = mParams.image;
-        mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
-
-        boolean savingToOtherUser = mParams.owner != Process.myUserHandle();
-        // Smart actions don't yet work for cross-user saves.
-        boolean smartActionsEnabled = !savingToOtherUser
-                && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
-                true);
-        try {
-            if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
-                // Since Quick Share target recommendation does not rely on image URL, it is
-                // queried and surfaced before image compress/export. Action intent would not be
-                // used, because it does not contain image URL.
-                Notification.Action quickShare =
-                        queryQuickShareAction(mScreenshotId, image, mParams.owner, null);
-                if (quickShare != null) {
-                    mQuickShareData.quickShareAction = quickShare;
-                    mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
-                }
-            }
-
-            // Call synchronously here since already on a background thread.
-            ListenableFuture<ImageExporter.Result> future =
-                    mImageExporter.export(Runnable::run, requestId, image, mParams.owner,
-                            mParams.displayId);
-            ImageExporter.Result result = future.get();
-            Log.d(TAG, "Saved screenshot: " + result);
-            final Uri uri = result.uri;
-            mImageTime = result.timestamp;
-
-            CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                    mScreenshotSmartActions.getSmartActionsFuture(
-                            mScreenshotId, uri, image, mSmartActionsProvider,
-                            ScreenshotSmartActionType.REGULAR_SMART_ACTIONS,
-                            smartActionsEnabled, mParams.owner);
-            List<Notification.Action> smartActions = new ArrayList<>();
-            if (smartActionsEnabled) {
-                int timeoutMs = DeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_SYSTEMUI,
-                        SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
-                        1000);
-                smartActions.addAll(buildSmartActions(
-                        mScreenshotSmartActions.getSmartActions(
-                                mScreenshotId, smartActionsFuture, timeoutMs,
-                                mSmartActionsProvider,
-                                ScreenshotSmartActionType.REGULAR_SMART_ACTIONS),
-                        mContext));
-            }
-
-            mImageData.uri = uri;
-            mImageData.owner = mParams.owner;
-            mImageData.smartActions = smartActions;
-            mImageData.quickShareAction = createQuickShareAction(
-                    mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
-                    mParams.owner);
-            mImageData.subject = getSubjectString(mImageTime);
-            mImageData.imageTime = mImageTime;
-
-            mParams.mActionsReadyListener.onActionsReady(mImageData);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
-                        + "finisher.accept(\"" + mImageData.uri + "\"");
-            }
-            mParams.finisher.accept(mImageData.uri);
-            mParams.image = null;
-        } catch (Exception e) {
-            // IOException/UnsupportedOperationException may be thrown if external storage is
-            // not mounted
-            Log.d(TAG, "Failed to store screenshot", e);
-            mParams.clearImage();
-            mImageData.reset();
-            mQuickShareData.reset();
-            mParams.mActionsReadyListener.onActionsReady(mImageData);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
-            }
-            mParams.finisher.accept(null);
-        }
-
-        return null;
-    }
-
-    /**
-     * Update the listener run when the saving task completes. Used to avoid showing UI for the
-     * first screenshot when a second one is taken.
-     */
-    void setActionsReadyListener(ActionsReadyListener listener) {
-        mParams.mActionsReadyListener = listener;
-    }
-
-    @Override
-    protected void onCancelled(Void params) {
-        // If we are cancelled while the task is running in the background, we may get null
-        // params. The finisher is expected to always be called back, so just use the baked-in
-        // params from the ctor in any case.
-        mImageData.reset();
-        mQuickShareData.reset();
-        mParams.mActionsReadyListener.onActionsReady(mImageData);
-        if (DEBUG_CALLBACK) {
-            Log.d(TAG, "onCancelled, calling (Consumer<Uri>) finisher.accept(null)");
-        }
-        mParams.finisher.accept(null);
-        mParams.clearImage();
-    }
-
-    private List<Notification.Action> buildSmartActions(
-            List<Notification.Action> actions, Context context) {
-        List<Notification.Action> broadcastActions = new ArrayList<>();
-        for (Notification.Action action : actions) {
-            // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
-            Bundle extras = action.getExtras();
-            String actionType = extras.getString(
-                    ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
-                    ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
-            Intent intent = new Intent(context, SmartActionsReceiver.class)
-                    .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, action.actionIntent)
-                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
-            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
-                    mRandom.nextInt(),
-                    intent,
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-            broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
-                    broadcastIntent).setContextual(true).addExtras(extras).build());
-        }
-        return broadcastActions;
-    }
-
-    private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
-            boolean smartActionsEnabled) {
-        intent
-                .putExtra(SmartActionsReceiver.EXTRA_ACTION_TYPE, actionType)
-                .putExtra(SmartActionsReceiver.EXTRA_ID, screenshotId)
-                .putExtra(SmartActionsReceiver.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
-    }
-
-    /**
-     * Wrap the quickshare intent and populate the fillin intent with the URI
-     */
-    @VisibleForTesting
-    Notification.Action createQuickShareAction(
-            Notification.Action quickShare, String screenshotId, Uri uri, long imageTime,
-            Bitmap image, UserHandle user) {
-        if (quickShare == null) {
-            return null;
-        } else if (quickShare.actionIntent.isImmutable()) {
-            Notification.Action quickShareWithUri =
-                    queryQuickShareAction(screenshotId, image, user, uri);
-            if (quickShareWithUri == null
-                    || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) {
-                return null;
-            }
-            quickShare = quickShareWithUri;
-        }
-
-        Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
-                .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, quickShare.actionIntent)
-                .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT_FILLIN,
-                        createFillInIntent(uri, imageTime))
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        Bundle extras = quickShare.getExtras();
-        String actionType = extras.getString(
-                ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
-                ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
-        // We only query for quick share actions when smart actions are enabled, so we can assert
-        // that it's true here.
-        addIntentExtras(screenshotId, wrappedIntent, actionType, true /* smartActionsEnabled */);
-        PendingIntent broadcastIntent =
-                PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
-                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title,
-                broadcastIntent)
-                .setContextual(true)
-                .addExtras(extras)
-                .build();
-    }
-
-    private Intent createFillInIntent(Uri uri, long imageTime) {
-        Intent fillIn = new Intent();
-        fillIn.setType("image/png");
-        fillIn.putExtra(Intent.EXTRA_STREAM, uri);
-        fillIn.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(imageTime));
-        // Include URI in ClipData also, so that grantPermission picks it up.
-        // We don't use setData here because some apps interpret this as "to:".
-        ClipData clipData = new ClipData(
-                new ClipDescription("content", new String[]{"image/png"}),
-                new ClipData.Item(uri));
-        fillIn.setClipData(clipData);
-        fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        return fillIn;
-    }
-
-    /**
-     * Query and surface Quick Share chip if it is available. Action intent would not be used,
-     * because it does not contain image URL which would be populated in {@link
-     * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)}
-     */
-
-    @VisibleForTesting
-    Notification.Action queryQuickShareAction(
-            String screenshotId, Bitmap image, UserHandle user, Uri uri) {
-        CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
-                mScreenshotSmartActions.getSmartActionsFuture(
-                        screenshotId, uri, image, mSmartActionsProvider,
-                        ScreenshotSmartActionType.QUICK_SHARE_ACTION,
-                        true /* smartActionsEnabled */, user);
-        int timeoutMs = DeviceConfig.getInt(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
-                500);
-        List<Notification.Action> quickShareActions =
-                mScreenshotSmartActions.getSmartActions(
-                        screenshotId, quickShareActionsFuture, timeoutMs,
-                        mSmartActionsProvider,
-                        ScreenshotSmartActionType.QUICK_SHARE_ACTION);
-        if (!quickShareActions.isEmpty()) {
-            return quickShareActions.get(0);
-        }
-        return null;
-    }
-
-    private static String getSubjectString(long imageTime) {
-        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
-        return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 7b802a2..fe58bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -18,7 +18,6 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static com.android.systemui.Flags.screenshotSaveImageExporter;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -124,7 +123,6 @@
     private final MessageContainerController mMessageContainerController;
     private final AnnouncementResolver mAnnouncementResolver;
     private Bitmap mScreenBitmap;
-    private SaveImageInBackgroundTask mSaveInBgTask;
     private boolean mScreenshotTakenInPortrait;
     private Animator mScreenshotAnimation;
     private RequestCallback mCurrentRequestCallback;
@@ -373,10 +371,6 @@
     // Any cleanup needed when the service is being destroyed.
     @Override
     public void onDestroy() {
-        if (mSaveInBgTask != null) {
-            // just log success/failure for the pre-existing screenshot
-            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
-        }
         removeWindow();
         releaseMediaPlayer();
         releaseContext();
@@ -525,36 +519,12 @@
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSoundIfNeeded();
 
-        if (screenshotSaveImageExporter()) {
-            saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
-                if (result.uri != null) {
-                    mScreenshotHandler.post(() -> Toast.makeText(mContext,
-                            R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
-                }
-            });
-        } else {
-            saveScreenshotInWorkerThread(
-                    screenshot.getUserHandle(),
-                    /* onComplete */ finisher,
-                    /* actionsReadyListener */ imageData -> {
-                        if (DEBUG_CALLBACK) {
-                            Log.d(TAG,
-                                    "returning URI to finisher (Consumer<URI>): " + imageData.uri);
-                        }
-                        finisher.accept(imageData.uri);
-                        if (imageData.uri == null) {
-                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
-                                    mPackageName);
-                            mNotificationsController.notifyScreenshotError(
-                                    R.string.screenshot_failed_to_save_text);
-                        } else {
-                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
-                            mScreenshotHandler.post(() -> Toast.makeText(mContext,
-                                    R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
-                        }
-                    },
-                    null);
-        }
+        saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+            if (result.uri != null) {
+                mScreenshotHandler.post(() -> Toast.makeText(mContext,
+                        R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+            }
+        });
     }
 
     /**
@@ -627,35 +597,6 @@
     }
 
     /**
-     * Creates a new worker thread and saves the screenshot to the media store.
-     */
-    private void saveScreenshotInWorkerThread(
-            UserHandle owner,
-            @NonNull Consumer<Uri> finisher,
-            @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
-            @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
-                    quickShareActionsReadyListener) {
-        SaveImageInBackgroundTask.SaveImageInBackgroundData
-                data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
-        data.image = mScreenBitmap;
-        data.finisher = finisher;
-        data.mActionsReadyListener = actionsReadyListener;
-        data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
-        data.owner = owner;
-        data.displayId = mDisplay.getDisplayId();
-
-        if (mSaveInBgTask != null) {
-            // just log success/failure for the pre-existing screenshot
-            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
-        }
-
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
-                mScreenshotSmartActions, data,
-                mScreenshotNotificationSmartActionsProvider);
-        mSaveInBgTask.execute();
-    }
-
-    /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
     private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
@@ -672,13 +613,6 @@
         }
     }
 
-    /**
-     * Logs success/failure of the screenshot saving task, and shows an error if it failed.
-     */
-    private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
-        logScreenshotResultStatus(imageData.uri, imageData.owner);
-    }
-
     private boolean isUserSetupComplete(UserHandle owner) {
         return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
                 .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 7425807..99ff946 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractorEmptyImpl
 import dagger.Binds
 import dagger.Module
 
@@ -75,4 +77,8 @@
     @Binds
     @SysUISingleton
     abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindShadeModeInteractor(impl: ShadeModeInteractorEmptyImpl): ShadeModeInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index da2024b..2348a11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -41,6 +41,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractorImpl
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractorImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -54,7 +56,7 @@
         @SysUISingleton
         fun provideBaseShadeInteractor(
             sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
-            sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
+            sceneContainerOff: Provider<ShadeInteractorLegacyImpl>,
         ): BaseShadeInteractor {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -67,7 +69,7 @@
         @SysUISingleton
         fun provideShadeController(
             sceneContainerOn: Provider<ShadeControllerSceneImpl>,
-            sceneContainerOff: Provider<ShadeControllerImpl>
+            sceneContainerOff: Provider<ShadeControllerImpl>,
         ): ShadeController {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -80,7 +82,7 @@
         @SysUISingleton
         fun provideShadeAnimationInteractor(
             sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
-            sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>
+            sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>,
         ): ShadeAnimationInteractor {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -93,7 +95,7 @@
         @SysUISingleton
         fun provideShadeBackActionInteractor(
             sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
-            sceneContainerOff: Provider<NotificationPanelViewController>
+            sceneContainerOff: Provider<NotificationPanelViewController>,
         ): ShadeBackActionInteractor {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -106,7 +108,7 @@
         @SysUISingleton
         fun provideShadeLockscreenInteractor(
             sceneContainerOn: Provider<ShadeLockscreenInteractorImpl>,
-            sceneContainerOff: Provider<NotificationPanelViewController>
+            sceneContainerOff: Provider<NotificationPanelViewController>,
         ): ShadeLockscreenInteractor {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -119,7 +121,7 @@
         @SysUISingleton
         fun providePanelExpansionInteractor(
             sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
-            sceneContainerOff: Provider<NotificationPanelViewController>
+            sceneContainerOff: Provider<NotificationPanelViewController>,
         ): PanelExpansionInteractor {
             return if (SceneContainerFlag.isEnabled) {
                 sceneContainerOn.get()
@@ -170,4 +172,8 @@
     @Binds
     @SysUISingleton
     abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindShadeModeInteractor(impl: ShadeModeInteractorImpl): ShadeModeInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 3cd91be..6fb96da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -145,7 +145,7 @@
 fun createAnyExpansionFlow(
     scope: CoroutineScope,
     shadeExpansion: Flow<Float>,
-    qsExpansion: Flow<Float>
+    qsExpansion: Flow<Float>,
 ): StateFlow<Float> {
     return combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
         .stateIn(scope, SharingStarted.Eagerly, 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index b8d2dd2..3eab02a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -25,9 +24,6 @@
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -55,11 +51,14 @@
     keyguardRepository: KeyguardRepository,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
-    private val shadeRepository: ShadeRepository,
     userSetupRepository: UserSetupRepository,
     userSwitcherInteractor: UserSwitcherInteractor,
     private val baseShadeInteractor: BaseShadeInteractor,
-) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
+    shadeModeInteractor: ShadeModeInteractor,
+) :
+    ShadeInteractor,
+    BaseShadeInteractor by baseShadeInteractor,
+    ShadeModeInteractor by shadeModeInteractor {
     override val isShadeEnabled: StateFlow<Boolean> =
         disableFlagsRepository.disableFlags
             .map { it.isShadeEnabled() }
@@ -103,27 +102,6 @@
             }
         }
 
-    override val isShadeLayoutWide: StateFlow<Boolean> = shadeRepository.isShadeLayoutWide
-
-    @FloatRange(from = 0.0, to = 1.0)
-    override fun getTopEdgeSplitFraction(): Float {
-        // Note: this implicitly relies on isShadeLayoutWide being hot (i.e. collected). This
-        // assumption allows us to query its value on demand (during swipe source detection) instead
-        // of running another infinite coroutine.
-        // TODO(b/338577208): Instead of being fixed at 0.8f, this should dynamically updated based
-        //  on the position of system-status icons in the status bar.
-        return if (shadeRepository.isShadeLayoutWide.value) 0.8f else 0.5f
-    }
-
-    override val shadeMode: StateFlow<ShadeMode> =
-        isShadeLayoutWide
-            .map(this::determineShadeMode)
-            .stateIn(
-                scope,
-                SharingStarted.Eagerly,
-                initialValue = determineShadeMode(isShadeLayoutWide.value)
-            )
-
     override val isExpandToQsEnabled: Flow<Boolean> =
         combine(
             disableFlagsRepository.disableFlags,
@@ -140,12 +118,4 @@
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
         }
-
-    private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
-        return when {
-            DualShade.isEnabled -> ShadeMode.Dual
-            isShadeLayoutWide -> ShadeMode.Split
-            else -> ShadeMode.Single
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
new file mode 100644
index 0000000..77ae679
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.annotation.FloatRange
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Defines interface for classes that can provide state and business logic related to the mode of
+ * the shade.
+ */
+interface ShadeModeInteractor {
+
+    /**
+     * The version of the shade layout to use.
+     *
+     * Note: Most likely, you want to read [isShadeLayoutWide] instead of this.
+     */
+    val shadeMode: StateFlow<ShadeMode>
+
+    /**
+     * Whether the shade layout should be wide (true) or narrow (false).
+     *
+     * In a wide layout, notifications and quick settings each take up only half the screen width
+     * (whether they are shown at the same time or not). In a narrow layout, they can each be as
+     * wide as the entire screen.
+     */
+    val isShadeLayoutWide: StateFlow<Boolean>
+
+    /**
+     * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
+     * between "top-left" and "top-right" for the purposes of dual-shade invocation.
+     *
+     * When the dual-shade is not wide, this always returns 0.5 (the top edge is evenly split). On
+     * wide layouts however, a larger fraction is returned because only the area of the system
+     * status icons is considered top-right.
+     *
+     * Note that this fraction only determines the split between the absolute left and right
+     * directions. In RTL layouts, the "top-start" edge will resolve to "top-right", and "top-end"
+     * will resolve to "top-left".
+     */
+    @FloatRange(from = 0.0, to = 1.0) fun getTopEdgeSplitFraction(): Float
+}
+
+class ShadeModeInteractorImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val repository: ShadeRepository,
+) : ShadeModeInteractor {
+
+    override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
+
+    override val shadeMode: StateFlow<ShadeMode> =
+        isShadeLayoutWide
+            .map(this::determineShadeMode)
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = determineShadeMode(isShadeLayoutWide.value),
+            )
+
+    @FloatRange(from = 0.0, to = 1.0)
+    override fun getTopEdgeSplitFraction(): Float {
+        // Note: this implicitly relies on isShadeLayoutWide being hot (i.e. collected). This
+        // assumption allows us to query its value on demand (during swipe source detection) instead
+        // of running another infinite coroutine.
+        // TODO(b/338577208): Instead of being fixed at 0.8f, this should dynamically updated based
+        //  on the position of system-status icons in the status bar.
+        return if (repository.isShadeLayoutWide.value) 0.8f else 0.5f
+    }
+
+    private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
+        return when {
+            DualShade.isEnabled -> ShadeMode.Dual
+            isShadeLayoutWide -> ShadeMode.Split
+            else -> ShadeMode.Single
+        }
+    }
+}
+
+class ShadeModeInteractorEmptyImpl @Inject constructor() : ShadeModeInteractor {
+
+    override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
+
+    override val isShadeLayoutWide: StateFlow<Boolean> = MutableStateFlow(false)
+
+    override fun getTopEdgeSplitFraction(): Float = 0.5f
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index f88fd7d..862f33bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -63,6 +63,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.annotations.KeepForWeakReference;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IStatusBar;
@@ -192,10 +193,10 @@
     private static final String SHOW_IME_SWITCHER_KEY = "showImeSwitcherKey";
 
     private final Object mLock = new Object();
-    private ArrayList<Callbacks> mCallbacks = new ArrayList<>();
-    private Handler mHandler = new H(Looper.getMainLooper());
+    private final ArrayList<Callbacks> mCallbacks = new ArrayList<>();
+    private final Handler mHandler = new H(Looper.getMainLooper());
     /** A map of display id - disable flag pair */
-    private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>();
+    private final SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>();
     /**
      * The last ID of the display where IME window for which we received setImeWindowStatus
      * event.
@@ -207,6 +208,21 @@
     private final @Nullable DumpHandler mDumpHandler;
     private final @Nullable Lazy<PowerInteractor> mPowerInteractor;
 
+    @KeepForWeakReference
+    private final DisplayTracker.Callback mDisplayTrackerCallback = new DisplayTracker.Callback() {
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            synchronized (mLock) {
+                mDisplayDisabled.remove(displayId);
+            }
+            // This callback is registered with {@link #mHandler} that already posts to run on
+            // main thread, so it is safe to dispatch directly.
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                mCallbacks.get(i).onDisplayRemoved(displayId);
+            }
+        }
+    };
+
     /**
      * These methods are called back on the main thread.
      */
@@ -576,19 +592,8 @@
         mDisplayTracker = displayTracker;
         mRegistry = registry;
         mDumpHandler = dumpHandler;
-        mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() {
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                synchronized (mLock) {
-                    mDisplayDisabled.remove(displayId);
-                }
-                // This callback is registered with {@link #mHandler} that already posts to run on
-                // main thread, so it is safe to dispatch directly.
-                for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                    mCallbacks.get(i).onDisplayRemoved(displayId);
-                }
-            }
-        }, new HandlerExecutor(mHandler));
+        mDisplayTracker.addDisplayChangeCallback(mDisplayTrackerCallback,
+                new HandlerExecutor(mHandler));
         // We always have default display.
         setDisabled(mDisplayTracker.getDefaultDisplayId(), DISABLE_NONE, DISABLE2_NONE);
         mPowerInteractor = powerInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 400f8af..dac0102 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.AirplaneModeTile
 import com.android.systemui.qs.tiles.BluetoothTile
@@ -95,21 +96,21 @@
     @IntoMap
     @StringKey(AIRPLANE_MODE_TILE_SPEC)
     fun provideAirplaneModeAvailabilityInteractor(
-            impl: AirplaneModeTileDataInteractor
+        impl: AirplaneModeTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(DATA_SAVER_TILE_SPEC)
     fun provideDataSaverAvailabilityInteractor(
-            impl: DataSaverTileDataInteractor
+        impl: DataSaverTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(INTERNET_TILE_SPEC)
     fun provideInternetAvailabilityInteractor(
-            impl: InternetTileDataInteractor
+        impl: InternetTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     companion object {
@@ -149,6 +150,7 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
                 policy = QSTilePolicy.Restricted(listOf(UserManager.DISALLOW_AIRPLANE_MODE)),
+                category = TileCategory.CONNECTIVITY,
             )
 
         /** Inject AirplaneModeTile into tileViewModelMap in QSModule */
@@ -180,6 +182,7 @@
                         labelRes = R.string.data_saver,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.CONNECTIVITY,
             )
 
         /** Inject DataSaverTile into tileViewModelMap in QSModule */
@@ -211,6 +214,7 @@
                         labelRes = R.string.quick_settings_internet_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.CONNECTIVITY,
             )
 
         /** Inject InternetTile into tileViewModelMap in QSModule */
@@ -242,6 +246,7 @@
                         labelRes = R.string.quick_settings_hotspot_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.CONNECTIVITY,
             )
 
         @Provides
@@ -256,6 +261,7 @@
                         labelRes = R.string.quick_settings_cast_title,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.CONNECTIVITY,
             )
 
         @Provides
@@ -270,6 +276,7 @@
                         labelRes = R.string.quick_settings_bluetooth_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.CONNECTIVITY,
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 0efd5f1..ec0827b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
 import com.android.systemui.util.time.SystemClock
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -279,7 +280,8 @@
     private val uiEventLogger: UiEventLogger,
     private val context: Context,
     private val notificationManager: NotificationManager,
-    private val logger: VisualInterruptionDecisionLogger
+    private val logger: VisualInterruptionDecisionLogger,
+    private val systemSettings: SystemSettings,
 ) :
     VisualInterruptionFilter(
         types = setOf(PEEK, PULSE),
@@ -300,6 +302,11 @@
     // education HUNs.
     private var hasShownOnceForDebug = false
 
+    // Sometimes the kotlin flow value is false even when the cooldown setting is true (b/356768397)
+    // so let's directly check settings until we confirm that the flow is initialized and in sync
+    // with the real settings value.
+    private var isCooldownFlowInSync = false
+
     private fun shouldShowEdu(): Boolean {
         val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1")
         return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug)
@@ -479,6 +486,15 @@
     }
 
     private fun isCooldownEnabled(): Boolean {
-        return settingsInteractor.isCooldownEnabled.value
+        val isEnabledFromFlow = settingsInteractor.isCooldownEnabled.value
+        if (isCooldownFlowInSync) {
+            return isEnabledFromFlow
+        }
+        val isEnabled =
+            systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) == 1
+        if (isEnabled == isEnabledFromFlow) {
+            isCooldownFlowInSync = true
+        }
+        return isEnabled
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 2f8711a..d4466f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -195,7 +195,8 @@
                     uiEventLogger,
                     context,
                     notificationManager,
-                    logger
+                    logger,
+                    systemSettings
                 )
             )
             avalancheProvider.register()
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 8ff1ab6..1214440a 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
@@ -5404,7 +5404,9 @@
             println(pw, "intrinsicContentHeight", mIntrinsicContentHeight);
             println(pw, "contentHeight", mContentHeight);
             println(pw, "intrinsicPadding", mIntrinsicPadding);
-            println(pw, "topPadding", getTopPadding());
+            if (!SceneContainerFlag.isEnabled()) {
+                println(pw, "topPadding", getTopPadding());
+            }
             println(pw, "bottomPadding", mBottomPadding);
             dumpRoundedRectClipping(pw);
             println(pw, "requestedClipBounds", mRequestedClipBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 1efad3b..0e7beb9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -262,6 +262,9 @@
             releaseAllImmediately();
             mReleaseOnExpandFinish = false;
         } else {
+            for (NotificationEntry entry: getAllEntries().toList()) {
+                entry.setSeenInShade(true);
+            }
             for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
                 if (isHeadsUpEntry(entry.getKey())) {
                     // Maybe the heads-up was removed already
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f3b9371..1ea26e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -61,6 +61,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags;
 import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -70,8 +71,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
 import com.android.systemui.keyguard.shared.model.Edge;
@@ -839,7 +840,7 @@
 
     public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
             boolean afterKeyguardGone, String message) {
-        if (SceneContainerFlag.isEnabled()) {
+        if (ComposeBouncerFlags.INSTANCE.isEnabled()) {
             if (r == null) {
                 return;
             }
@@ -891,7 +892,7 @@
                     return;
                 }
 
-                if (!SceneContainerFlag.isEnabled()) {
+                if (!ComposeBouncerFlags.INSTANCE.isEnabled()) {
                     mAfterKeyguardGoneAction = r;
                     mKeyguardGoneCancelAction = cancelAction;
                     mDismissActionWillAnimateOnKeyguard = r != null
@@ -965,7 +966,7 @@
      * Adds a {@param runnable} to be executed after Keyguard is gone.
      */
     public void addAfterKeyguardGoneRunnable(Runnable runnable) {
-        if (SceneContainerFlag.isEnabled()) {
+        if (ComposeBouncerFlags.INSTANCE.isEnabled()) {
             if (runnable != null) {
                 mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
             }
@@ -1180,7 +1181,7 @@
             // We update the state (which will show the keyguard) only if an animation will run on
             // the keyguard. If there is no animation, we wait before updating the state so that we
             // go directly from bouncer to launcher/app.
-            if (SceneContainerFlag.isEnabled()) {
+            if (ComposeBouncerFlags.INSTANCE.isEnabled()) {
                 if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
                     updateStates();
                 }
@@ -1307,7 +1308,7 @@
     }
 
     private void executeAfterKeyguardGoneAction() {
-        if (SceneContainerFlag.isEnabled()) {
+        if (ComposeBouncerFlags.INSTANCE.isEnabled()) {
             return;
         }
         if (mAfterKeyguardGoneAction != null) {
@@ -1703,6 +1704,8 @@
         pw.println("  Registered KeyguardViewManagerCallbacks:");
         pw.println(" SceneContainerFlag enabled:"
                 + SceneContainerFlag.isEnabled());
+        pw.println(" ComposeBouncerFlags enabled:"
+                + ComposeBouncerFlags.INSTANCE.isEnabled());
         for (KeyguardViewManagerCallback callback : mCallbacks) {
             pw.println("      " + callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index c046168..0ad1042a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -480,10 +480,16 @@
     }
 
     public static AlertDialog applyFlags(AlertDialog dialog) {
+        return applyFlags(dialog, true);
+    }
+
+    public static AlertDialog applyFlags(AlertDialog dialog, boolean showWhenLocked) {
         final Window window = dialog.getWindow();
         window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
-        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        if (showWhenLocked) {
+            window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        }
         window.getAttributes().setFitInsetsTypes(
                 window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
         return dialog;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index fc7a672..bc7d376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -64,9 +64,6 @@
         const val COL_NAME_IS_ENABLED = "isEnabled"
         /** Column name to use for [isWifiDefault] for table logging. */
         const val COL_NAME_IS_DEFAULT = "isDefault"
-
-        const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
-            "Wifi network was carrier merged but had invalid sub ID"
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 7163e67..f4bb1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -46,7 +46,7 @@
     private val _isWifiDefault = MutableStateFlow(false)
     override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
 
-    private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
+    private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive())
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
     private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
@@ -82,7 +82,7 @@
         _isWifiEnabled.value = false
         _isWifiDefault.value = false
         _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
-        _wifiNetwork.value = WifiNetworkModel.Inactive
+        _wifiNetwork.value = WifiNetworkModel.Inactive()
     }
 
     private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
@@ -100,7 +100,7 @@
     }
 
     private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
-        WifiNetworkModel.Active(
+        WifiNetworkModel.Active.of(
             isValidated = validated ?: true,
             level = level ?: 0,
             ssid = ssid ?: DEMO_NET_SSID,
@@ -108,7 +108,7 @@
         )
 
     private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel =
-        WifiNetworkModel.CarrierMerged(
+        WifiNetworkModel.CarrierMerged.of(
             subscriptionId = subscriptionId,
             level = level,
             numberOfLevels = numberOfLevels,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index b6e73e0..76024cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint
 import android.net.wifi.ScanResult
 import android.net.wifi.WifiManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
@@ -39,18 +38,14 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Unavailable.toHotspotDeviceType
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import com.android.wifitrackerlib.HotspotNetworkEntry
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 import com.android.wifitrackerlib.WifiPickerTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -246,36 +241,28 @@
     }
 
     private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
-        return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
-            WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
-        } else {
-            WifiNetworkModel.CarrierMerged(
-                subscriptionId = this.subscriptionId,
-                level = this.level,
-                // WifiManager APIs to calculate the signal level start from 0, so
-                // maxSignalLevel + 1 represents the total level buckets count.
-                numberOfLevels = wifiManager.maxSignalLevel + 1,
-            )
-        }
+        // WifiEntry instance values aren't guaranteed to be stable between method calls
+        // because
+        // WifiPickerTracker is continuously updating the same object. Save the level in a
+        // local
+        // variable so that checking the level validity here guarantees that the level will
+        // still be
+        // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the
+        // level
+        // could be valid here but become invalid later, and `WifiNetworkModel.Active` will
+        // throw
+        // an exception. See b/362384551.
+
+        return WifiNetworkModel.CarrierMerged.of(
+            subscriptionId = this.subscriptionId,
+            level = this.level,
+            // WifiManager APIs to calculate the signal level start from 0, so
+            // maxSignalLevel + 1 represents the total level buckets count.
+            numberOfLevels = wifiManager.maxSignalLevel + 1,
+        )
     }
 
     private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
-        // WifiEntry instance values aren't guaranteed to be stable between method calls because
-        // WifiPickerTracker is continuously updating the same object. Save the level in a local
-        // variable so that checking the level validity here guarantees that the level will still be
-        // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the level
-        // could be valid here but become invalid later, and `WifiNetworkModel.Active` will throw
-        // an exception. See b/362384551.
-        val currentLevel = this.level
-        if (
-            currentLevel == WIFI_LEVEL_UNREACHABLE ||
-                currentLevel !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX
-        ) {
-            // If our level means the network is unreachable or the level is otherwise invalid, we
-            // don't have an active network.
-            return WifiNetworkModel.Inactive
-        }
-
         val hotspotDeviceType =
             if (this is HotspotNetworkEntry) {
                 this.deviceType.toHotspotDeviceType()
@@ -283,9 +270,9 @@
                 WifiNetworkModel.HotspotDeviceType.NONE
             }
 
-        return WifiNetworkModel.Active(
+        return WifiNetworkModel.Active.of(
             isValidated = this.hasInternetAccess(),
-            level = currentLevel,
+            level = this.level,
             ssid = this.title,
             hotspotDeviceType = hotspotDeviceType,
         )
@@ -421,7 +408,7 @@
 
     companion object {
         // Start out with no known wifi network.
-        @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+        @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive()
 
         private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 39842fb..3220377 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
+import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
 import android.telephony.SubscriptionManager
@@ -23,8 +24,12 @@
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.isValid
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.of
 import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
 import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
@@ -64,7 +69,7 @@
         /** A description of why the wifi information was invalid. */
         val invalidReason: String,
     ) : WifiNetworkModel() {
-        override fun toString() = "WifiNetwork.Invalid[$invalidReason]"
+        override fun toString() = "WifiNetwork.Invalid[reason=$invalidReason]"
 
         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
             if (prevVal !is Invalid) {
@@ -73,12 +78,12 @@
             }
 
             if (invalidReason != prevVal.invalidReason) {
-                row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
+                row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]")
             }
         }
 
         override fun logFull(row: TableRowLogger) {
-            row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
+            row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]")
             row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
             row.logChange(COL_VALIDATED, false)
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
@@ -89,20 +94,25 @@
     }
 
     /** A model representing that we have no active wifi network. */
-    object Inactive : WifiNetworkModel() {
-        override fun toString() = "WifiNetwork.Inactive"
+    data class Inactive(
+        /** An optional description of why the wifi information was inactive. */
+        val inactiveReason: String? = null,
+    ) : WifiNetworkModel() {
+        override fun toString() = "WifiNetwork.Inactive[reason=$inactiveReason]"
 
         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
-            if (prevVal is Inactive) {
+            if (prevVal !is Inactive) {
+                logFull(row)
                 return
             }
 
-            // When changing to Inactive, we need to log diffs to all the fields.
-            logFull(row)
+            if (inactiveReason != prevVal.inactiveReason) {
+                row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]")
+            }
         }
 
         override fun logFull(row: TableRowLogger) {
-            row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+            row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]")
             row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
             row.logChange(COL_VALIDATED, false)
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
@@ -117,31 +127,71 @@
      * treated as more of a mobile network.
      *
      * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
+     *
+     * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] methods. [of]
+     * will verify preconditions correctly.
      */
-    data class CarrierMerged(
+    data class CarrierMerged
+    private constructor(
         /**
          * The subscription ID that this connection represents.
          *
          * Comes from [android.net.wifi.WifiInfo.getSubscriptionId].
          *
-         * Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid,
-         * then this is *not* a carrier merged network).
+         * Per that method, this value must not be [SubscriptionManager.INVALID_SUBSCRIPTION_ID] (if
+         * it was invalid, then this is *not* a carrier merged network).
          */
         val subscriptionId: Int,
 
-        /** The signal level, guaranteed to be 0 <= level <= numberOfLevels. */
+        /** The signal level, required to be 0 <= level <= numberOfLevels. */
         val level: Int,
 
         /** The maximum possible level. */
-        val numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS,
+        val numberOfLevels: Int,
     ) : WifiNetworkModel() {
-        init {
-            require(level in MIN_VALID_LEVEL..numberOfLevels) {
-                "CarrierMerged: $MIN_VALID_LEVEL <= wifi level <= $numberOfLevels required; " +
+        companion object {
+            /**
+             * Creates a [CarrierMerged] instance, or an [Invalid] instance if any of the arguments
+             * are invalid.
+             */
+            fun of(
+                subscriptionId: Int,
+                level: Int,
+                numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS
+            ): WifiNetworkModel {
+                if (!subscriptionId.isSubscriptionIdValid()) {
+                    return Invalid(INVALID_SUB_ID_ERROR_STRING)
+                }
+                if (!level.isLevelValid(numberOfLevels)) {
+                    return Invalid(getInvalidLevelErrorString(level, numberOfLevels))
+                }
+                return CarrierMerged(subscriptionId, level, numberOfLevels)
+            }
+
+            private fun Int.isLevelValid(maxLevel: Int): Boolean {
+                return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..maxLevel
+            }
+
+            private fun getInvalidLevelErrorString(level: Int, maxLevel: Int): String {
+                return "Wifi network was carrier merged but had invalid level. " +
+                    "$MIN_VALID_LEVEL <= wifi level <= $maxLevel required; " +
                     "level was $level"
             }
-            require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                "subscription ID cannot be invalid"
+
+            private fun Int.isSubscriptionIdValid(): Boolean {
+                return this != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+            }
+
+            private const val INVALID_SUB_ID_ERROR_STRING =
+                "Wifi network was carrier merged but had invalid sub ID"
+        }
+
+        init {
+            require(level.isLevelValid(numberOfLevels)) {
+                "${getInvalidLevelErrorString(level, numberOfLevels)}. $DO_NOT_USE_COPY_ERROR"
+            }
+            require(subscriptionId.isSubscriptionIdValid()) {
+                "$INVALID_SUB_ID_ERROR_STRING. $DO_NOT_USE_COPY_ERROR"
             }
         }
 
@@ -173,28 +223,64 @@
         }
     }
 
-    /** Provides information about an active wifi network. */
-    data class Active(
+    /**
+     * Provides information about an active wifi network.
+     *
+     * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] method. [of]
+     * will verify preconditions correctly.
+     */
+    data class Active
+    private constructor(
         /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */
-        val isValidated: Boolean = false,
+        val isValidated: Boolean,
 
-        /** The wifi signal level, guaranteed to be 0 <= level <= 4. */
+        /** The wifi signal level, required to be 0 <= level <= 4. */
         val level: Int,
 
         /** See [android.net.wifi.WifiInfo.ssid]. */
-        val ssid: String? = null,
+        val ssid: String?,
 
         /**
          * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this
          * isn't a hotspot connection.
          */
-        val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+        val hotspotDeviceType: HotspotDeviceType,
     ) : WifiNetworkModel() {
-        init {
-            require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
-                "Active: $MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " +
+        companion object {
+            /**
+             * Creates an [Active] instance, or an [Inactive] instance if any of the arguments are
+             * invalid.
+             */
+            @JvmStatic
+            fun of(
+                isValidated: Boolean = false,
+                level: Int,
+                ssid: String? = null,
+                hotspotDeviceType: HotspotDeviceType = HotspotDeviceType.NONE,
+            ): WifiNetworkModel {
+                if (!level.isValid()) {
+                    return Inactive(getInvalidLevelErrorString(level))
+                }
+                return Active(isValidated, level, ssid, hotspotDeviceType)
+            }
+
+            private fun Int.isValid(): Boolean {
+                return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..MAX_VALID_LEVEL
+            }
+
+            private fun getInvalidLevelErrorString(level: Int): String {
+                return "Wifi network was active but had invalid level. " +
+                    "$MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " +
                     "level was $level"
             }
+
+            @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX
+        }
+
+        init {
+            require(level.isValid()) {
+                "${getInvalidLevelErrorString(level)}. $DO_NOT_USE_COPY_ERROR"
+            }
         }
 
         /** Returns true if this network has a valid SSID and false otherwise. */
@@ -231,10 +317,6 @@
             row.logChange(COL_SSID, ssid)
             row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
         }
-
-        companion object {
-            @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX
-        }
     }
 
     companion object {
@@ -292,3 +374,7 @@
 val LEVEL_DEFAULT: String? = null
 val NUM_LEVELS_DEFAULT: String? = null
 val SUB_ID_DEFAULT: String? = null
+
+private const val DO_NOT_USE_COPY_ERROR =
+    "This should only be an issue if the caller incorrectly used `copy` to get a new instance. " +
+        "Please use the `of` method instead."
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 591d7af..cf9f9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.AlarmTile
 import com.android.systemui.qs.tiles.CameraToggleTile
@@ -157,6 +158,7 @@
                         labelRes = R.string.quick_settings_flashlight_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
             )
 
         /** Inject FlashlightTile into tileViewModelMap in QSModule */
@@ -192,7 +194,8 @@
                 policy =
                     QSTilePolicy.Restricted(
                         listOf(DISALLOW_SHARE_LOCATION, DISALLOW_CONFIG_LOCATION)
-                    )
+                    ),
+                category = TileCategory.PRIVACY,
             )
 
         /** Inject LocationTile into tileViewModelMap in QSModule */
@@ -225,6 +228,7 @@
                         labelRes = R.string.status_bar_alarm,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
             )
 
         /** Inject AlarmTile into tileViewModelMap in QSModule */
@@ -257,6 +261,7 @@
                         labelRes = R.string.quick_settings_ui_mode_night_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.DISPLAY,
             )
 
         /** Inject uimodenight into tileViewModelMap in QSModule */
@@ -290,6 +295,7 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
                 autoRemoveOnUnavailable = false,
+                category = TileCategory.PRIVACY,
             )
 
         /** Inject work mode into tileViewModelMap in QSModule */
@@ -323,6 +329,7 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
                 policy = QSTilePolicy.Restricted(listOf(DISALLOW_CAMERA_TOGGLE)),
+                category = TileCategory.PRIVACY,
             )
 
         /** Inject camera toggle tile into tileViewModelMap in QSModule */
@@ -365,6 +372,7 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
                 policy = QSTilePolicy.Restricted(listOf(DISALLOW_MICROPHONE_TOGGLE)),
+                category = TileCategory.PRIVACY,
             )
 
         /** Inject microphone toggle tile into tileViewModelMap in QSModule */
@@ -407,6 +415,7 @@
                             labelRes = R.string.quick_settings_modes_label,
                         ),
                     instanceId = uiEventLogger.getNewInstanceId(),
+                    category = TileCategory.CONNECTIVITY,
                 )
             } else {
                 QSTileConfig(
@@ -417,6 +426,7 @@
                             labelRes = R.string.quick_settings_dnd_label,
                         ),
                     instanceId = uiEventLogger.getNewInstanceId(),
+                    category = TileCategory.CONNECTIVITY,
                 )
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
index ea213cb..dd1c11d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
+import com.android.systemui.qs.shared.model.TileCategory;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.QuickAccessWalletTile;
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig;
@@ -34,8 +35,6 @@
 import com.android.systemui.wallet.controller.WalletContextualLocationsService;
 import com.android.systemui.wallet.ui.WalletActivity;
 
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -43,6 +42,8 @@
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.StringKey;
 
+import java.util.concurrent.Executor;
+
 /**
  * Module for injecting classes in Wallet.
  */
@@ -90,6 +91,7 @@
                         R.string.wallet_title
                 ),
                 uiEventLogger.getNewInstanceId(),
+                TileCategory.UTILITIES,
                 tileSpec.getSpec(),
                 QSTilePolicy.NoRestrictions.INSTANCE
         );
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index d85b774..bf13ceb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -445,7 +445,7 @@
 
         assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
         mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
+                WifiNetworkModel.Active.Companion.of(
                         /* isValidated= */ false,
                         /* level= */ 0,
                         /* ssid= */ "",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
index 4a5c1be..038ec40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
@@ -32,12 +32,14 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -66,7 +68,7 @@
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, MY_USER_ID);
         mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext,
-                mUserTracker);
+                mUserTracker, Mockito.mock(SecureSettings.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
index a5a7a4a..f564926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
@@ -31,12 +31,14 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -62,7 +64,7 @@
     public void setUp() {
         when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
         mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext,
-                mUserTracker);
+                mUserTracker, Mockito.mock(SecureSettings.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
index ba990ef..afed12f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
@@ -31,12 +31,14 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -62,7 +64,7 @@
     public void setUp() {
         when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
         mAccessibilityGestureTargetsObserver = new AccessibilityGestureTargetsObserver(mContext,
-                mUserTracker);
+                mUserTracker, Mockito.mock(SecureSettings.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
index 9222fc2..1d88b90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -72,7 +73,7 @@
 
         protected FakeSecureSettingsContentObserver(Context context, UserTracker userTracker,
                 String secureSettingsKey) {
-            super(context, userTracker, secureSettingsKey);
+            super(context, userTracker, Mockito.mock(SecureSettings.class), secureSettingsKey);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 55fd344..4850510 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.sideFpsOverlayInteractor
 import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
 import com.android.systemui.biometrics.extractAuthenticatorTypes
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -1453,11 +1454,15 @@
     @Test
     fun switch_to_credential_fallback() = runGenericTest {
         val size by collectLastValue(kosmos.promptViewModel.size)
+        val isShowingSfpsIndicator by collectLastValue(kosmos.sideFpsOverlayInteractor.isShowing)
 
         // TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
         kosmos.promptViewModel.onSwitchToCredential()
 
         assertThat(size).isEqualTo(PromptSize.LARGE)
+        if (testCase.modalities.hasSfps) {
+            assertThat(isShowingSfpsIndicator).isFalse()
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
new file mode 100644
index 0000000..9da6885
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial
+
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.testKosmos
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadTutorialCoreStartableTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val broadcastDispatcher = kosmos.broadcastDispatcher
+    private val context = mock<Context>()
+    private val underTest =
+        KeyboardTouchpadTutorialCoreStartable(
+            { mock<TutorialNotificationCoordinator>() },
+            broadcastDispatcher,
+            context
+        )
+
+    @Test
+    fun registersBroadcastReceiverStartingActivityAsSystemUser() {
+        underTest.start()
+
+        broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL")
+        )
+
+        verify(context).startActivityAsUser(any(), eq(UserHandle.SYSTEM))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 3cbbb64..bea415c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -42,6 +42,7 @@
 import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -77,12 +78,14 @@
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
                 dismissInteractor = dismissInteractor,
                 applicationScope = testScope.backgroundScope,
-                sceneInteractor = kosmos.sceneInteractor,
-                deviceEntryInteractor = kosmos.deviceEntryInteractor,
-                quickSettingsSceneFamilyResolver = kosmos.quickSettingsSceneFamilyResolver,
-                notifShadeSceneFamilyResolver = kosmos.notifShadeSceneFamilyResolver,
+                sceneInteractor = { kosmos.sceneInteractor },
+                deviceEntryInteractor = { kosmos.deviceEntryInteractor },
+                quickSettingsSceneFamilyResolver = { kosmos.quickSettingsSceneFamilyResolver },
+                notifShadeSceneFamilyResolver = { kosmos.notifShadeSceneFamilyResolver },
                 powerInteractor = kosmos.powerInteractor,
                 alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
+                keyguardInteractor = { kosmos.keyguardInteractor },
+                shadeInteractor = { kosmos.shadeInteractor },
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 3c74374..823a23d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,6 +38,8 @@
 import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Bundle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
@@ -61,6 +63,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.mockMediaLogger
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
 import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.shared.model.MediaData
@@ -69,7 +73,6 @@
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
-import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.testKosmos
@@ -186,11 +189,10 @@
         mSetFlagsRule.setFlagsParameterization(flags)
     }
 
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
     private val testDispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
     private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
-    private val activityStarter = kosmos.activityStarter
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
@@ -240,7 +242,6 @@
                 mediaDeviceManager = mediaDeviceManager,
                 mediaDataCombineLatest = mediaDataCombineLatest,
                 mediaDataFilter = mediaDataFilter,
-                activityStarter = activityStarter,
                 smartspaceMediaDataProvider = smartspaceMediaDataProvider,
                 useMediaResumption = true,
                 useQsMediaPlayer = true,
@@ -251,6 +252,7 @@
                 smartspaceManager = smartspaceManager,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
                 mediaDataLoader = { kosmos.mediaDataLoader },
+                mediaLogger = kosmos.mediaLogger,
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -2404,6 +2406,45 @@
         assertThat(mediaDataCaptor.value.artwork).isNull()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION)
+    fun postDuplicateNotification_doesNotCallListeners() {
+        addNotificationAndLoad()
+        reset(listener)
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+        testScope.assertRunAllReady(foreground = 0, background = 1)
+        verify(listener, never())
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION)
+    fun postDuplicateNotification_callsListeners() {
+        addNotificationAndLoad()
+        reset(listener)
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY))
+    }
+
     private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
         runCurrent()
         if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 6a66c40..0c8d880 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -125,6 +126,8 @@
     private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
@@ -141,6 +144,7 @@
                 { localBluetoothManager },
                 fakeFgExecutor,
                 fakeBgExecutor,
+                kosmos.mediaDeviceLogger,
             )
         manager.addListener(listener)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
index 02d7413..bc29d2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
@@ -138,6 +138,7 @@
         whenever(mockContext.packageManager).thenReturn(context.packageManager)
         whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
         whenever(mockContext.userId).thenReturn(context.userId)
+        whenever(mockContext.resources).thenReturn(context.resources)
         whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
 
         executor = FakeExecutor(clock)
@@ -210,7 +211,7 @@
     @Test
     fun testOnLoad_checksForResume_noService() {
         // When media data is loaded that has not been checked yet, and does not have a MBS
-        resumeListener.onMediaDataLoaded(KEY, null, data)
+        onMediaDataLoaded(KEY, null, data)
 
         // Then we report back to the manager
         verify(mediaDataManager).setResumeAction(KEY, null)
@@ -223,8 +224,7 @@
         whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
 
         // When media data is loaded that has not been checked yet, and does not have a MBS
-        resumeListener.onMediaDataLoaded(KEY, null, data)
-        executor.runAllReady()
+        onMediaDataLoaded(KEY, null, data)
 
         // Then we report back to the manager
         verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
@@ -234,7 +234,7 @@
     fun testOnLoad_localCast_doesNotCheck() {
         // When media data is loaded that has not been checked yet, and is a local cast
         val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCast)
+        onMediaDataLoaded(KEY, null, dataCast, false)
 
         // Then we do not take action
         verify(mediaDataManager, never()).setResumeAction(any(), any())
@@ -244,7 +244,7 @@
     fun testOnload_remoteCast_doesNotCheck() {
         // When media data is loaded that has not been checked yet, and is a remote cast
         val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE)
-        resumeListener.onMediaDataLoaded(KEY, null, dataRcn)
+        onMediaDataLoaded(KEY, null, dataRcn, resume = false)
 
         // Then we do not take action
         verify(mediaDataManager, never()).setResumeAction(any(), any())
@@ -257,7 +257,7 @@
 
         // When media data is loaded that has not been checked yet, and is a local cast
         val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCast)
+        onMediaDataLoaded(KEY, null, dataCast)
 
         // Then we report back to the manager
         verify(mediaDataManager).setResumeAction(KEY, null)
@@ -270,7 +270,7 @@
 
         // When media data is loaded that has not been checked yet, and is a remote cast
         val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE)
-        resumeListener.onMediaDataLoaded(KEY, null, dataRcn)
+        onMediaDataLoaded(KEY, null, dataRcn, false)
 
         // Then we do not take action
         verify(mediaDataManager, never()).setResumeAction(any(), any())
@@ -288,10 +288,9 @@
 
         // When media data is loaded that has not been checked yet, and does have a MBS
         val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+        onMediaDataLoaded(KEY, null, dataCopy)
 
         // Then we test whether the service is valid
-        executor.runAllReady()
         verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
         verify(resumeBrowser).testConnection()
 
@@ -307,7 +306,7 @@
     fun testOnLoad_doesNotCheckAgain() {
         // When a media data is loaded that has been checked already
         var dataCopy = data.copy(hasCheckedForResume = true)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+        onMediaDataLoaded(KEY, null, dataCopy, resume = false)
 
         // Then we should not check it again
         verify(resumeBrowser, never()).testConnection()
@@ -320,17 +319,15 @@
         setUpMbsWithValidResolveInfo()
         resumeListener.onMediaDataLoaded(KEY, null, data)
 
-        // We notify the manager to set a null action
-        verify(mediaDataManager).setResumeAction(KEY, null)
-
         // If we then get another update from the app before the first check completes
         assertThat(executor.numPending()).isEqualTo(1)
         var dataWithCheck = data.copy(hasCheckedForResume = true)
         resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck)
 
         // We do not try to start another check
-        assertThat(executor.numPending()).isEqualTo(1)
+        executor.runAllReady()
         verify(mediaDataManager).setResumeAction(KEY, null)
+        verify(resumeBrowserFactory, times(1)).create(any(), any(), anyInt())
     }
 
     @Test
@@ -363,6 +360,7 @@
         resumeListener.userUnlockReceiver.onReceive(context, intent)
 
         // Then we should attempt to find recent media for each saved component
+        executor.runAllReady()
         verify(resumeBrowser, times(3)).findRecentMedia()
 
         // Then since the mock service found media, the manager should be informed
@@ -382,10 +380,9 @@
 
         // When media data is loaded that has not been checked yet, and does have a MBS
         val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+        onMediaDataLoaded(KEY, null, dataCopy)
 
         // Then we test whether the service is valid and set the resume action
-        executor.runAllReady()
         verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
         verify(resumeBrowser).testConnection()
         verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor))
@@ -455,6 +452,7 @@
         resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
 
         // We add its resume controls
+        executor.runAllReady()
         verify(resumeBrowser).findRecentMedia()
         verify(mediaDataManager)
             .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
@@ -527,7 +525,7 @@
 
         // When media data is loaded that has not been checked yet, and does have a MBS
         val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+        onMediaDataLoaded(KEY, null, dataCopy)
 
         // Then we store the new lastPlayed time
         verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
@@ -546,10 +544,9 @@
     fun testOnMediaDataLoaded_newKeyDifferent_oldMediaBrowserDisconnected() {
         setUpMbsWithValidResolveInfo()
 
-        resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
-        executor.runAllReady()
+        onMediaDataLoaded(key = KEY, oldKey = null, data)
 
-        resumeListener.onMediaDataLoaded(key = "newKey", oldKey = KEY, data)
+        onMediaDataLoaded(key = "newKey", oldKey = KEY, data)
 
         verify(resumeBrowser).disconnect()
     }
@@ -561,8 +558,7 @@
         // Set up mocks to return with an error
         whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
 
-        resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
-        executor.runAllReady()
+        onMediaDataLoaded(key = KEY, oldKey = null, data)
 
         // Ensure we disconnect the browser
         verify(resumeBrowser).disconnect()
@@ -579,8 +575,7 @@
             callbackCaptor.value.addTrack(description, component, resumeBrowser)
         }
 
-        resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
-        executor.runAllReady()
+        onMediaDataLoaded(key = KEY, oldKey = null, data)
 
         // Ensure we disconnect the browser
         verify(resumeBrowser).disconnect()
@@ -598,8 +593,7 @@
 
         // Load media data that will require us to get the resume action
         val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
-        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
-        executor.runAllReady()
+        onMediaDataLoaded(KEY, null, dataCopy)
         verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor))
 
         // Set up our factory to return a new browser so we can verify we disconnected the old one
@@ -634,6 +628,7 @@
         // When the first user unlocks and we query their recent media
         userCallbackCaptor.value.onUserChanged(firstUserId, context)
         resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
+        executor.runAllReady()
         whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value)
         verify(resumeBrowser, times(3)).findRecentMedia()
 
@@ -688,4 +683,16 @@
         whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo)
         whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
     }
+
+    private fun onMediaDataLoaded(
+        key: String,
+        oldKey: String?,
+        data: MediaData,
+        resume: Boolean = true
+    ) {
+        resumeListener.onMediaDataLoaded(key, oldKey, data)
+        if (resume) {
+            assertThat(executor.runAllReady()).isEqualTo(1)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index d9faa30..70af5e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -31,17 +31,18 @@
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.text.AnnotatedString
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -199,10 +200,11 @@
                             android.R.drawable.star_on,
                             ContentDescription.Loaded(tileSpec)
                         ),
-                    label = Text.Loaded(tileSpec),
+                    label = AnnotatedString(tileSpec),
                     appName = null,
                     isCurrent = true,
                     availableEditActions = emptySet(),
+                    category = TileCategory.UNKNOWN,
                 ),
                 getWidth(tileSpec),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index e6ec07e..9f84e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -258,7 +258,7 @@
     companion object {
         const val WIFI_SSID = "test ssid"
         val ACTIVE_WIFI =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 isValidated = true,
                 level = 4,
                 ssid = WIFI_SSID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
deleted file mode 100644
index 5e07aef..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.drawable.Icon
-import android.net.Uri
-import android.os.UserHandle
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import java.util.concurrent.CompletableFuture
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class SaveImageInBackgroundTaskTest : SysuiTestCase() {
-    private val imageExporter = mock<ImageExporter>()
-    private val smartActions = mock<ScreenshotSmartActions>()
-    private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
-    private val saveImageData = SaveImageInBackgroundTask.SaveImageInBackgroundData()
-    private val testScreenshotId: String = "testScreenshotId"
-    private val testBitmap = mock<Bitmap>()
-    private val testUser = UserHandle.getUserHandleForUid(0)
-    private val testIcon = mock<Icon>()
-    private val testImageTime = 1234.toLong()
-    private val flags = FakeFeatureFlags()
-
-    private val smartActionsUriFuture = mock<CompletableFuture<List<Notification.Action>>>()
-    private val smartActionsFuture = mock<CompletableFuture<List<Notification.Action>>>()
-
-    private val testUri: Uri = Uri.parse("testUri")
-    private val intent =
-        Intent(Intent.ACTION_SEND)
-            .setComponent(
-                ComponentName.unflattenFromString(
-                    "com.google.android.test/com.google.android.test.TestActivity"
-                )
-            )
-    private val immutablePendingIntent =
-        PendingIntent.getBroadcast(
-            mContext,
-            0,
-            intent,
-            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
-        )
-    private val mutablePendingIntent =
-        PendingIntent.getBroadcast(
-            mContext,
-            0,
-            intent,
-            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
-        )
-
-    private val saveImageTask =
-        SaveImageInBackgroundTask(
-            mContext,
-            flags,
-            imageExporter,
-            smartActions,
-            saveImageData,
-            smartActionsProvider,
-        )
-
-    @Before
-    fun setup() {
-        whenever(
-                smartActions.getSmartActionsFuture(
-                    eq(testScreenshotId),
-                    any(Uri::class.java),
-                    eq(testBitmap),
-                    eq(smartActionsProvider),
-                    any(ScreenshotSmartActionType::class.java),
-                    any(Boolean::class.java),
-                    eq(testUser)
-                )
-            )
-            .thenReturn(smartActionsUriFuture)
-        whenever(
-                smartActions.getSmartActionsFuture(
-                    eq(testScreenshotId),
-                    eq(null),
-                    eq(testBitmap),
-                    eq(smartActionsProvider),
-                    any(ScreenshotSmartActionType::class.java),
-                    any(Boolean::class.java),
-                    eq(testUser)
-                )
-            )
-            .thenReturn(smartActionsFuture)
-    }
-
-    @Test
-    fun testQueryQuickShare_noAction() {
-        whenever(
-                smartActions.getSmartActions(
-                    eq(testScreenshotId),
-                    eq(smartActionsFuture),
-                    any(Int::class.java),
-                    eq(smartActionsProvider),
-                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
-                )
-            )
-            .thenReturn(ArrayList<Notification.Action>())
-
-        val quickShareAction =
-            saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)
-
-        assertNull(quickShareAction)
-    }
-
-    @Test
-    fun testQueryQuickShare_withActions() {
-        val actions = ArrayList<Notification.Action>()
-        actions.add(constructAction("Action One", mutablePendingIntent))
-        actions.add(constructAction("Action Two", mutablePendingIntent))
-        whenever(
-                smartActions.getSmartActions(
-                    eq(testScreenshotId),
-                    eq(smartActionsUriFuture),
-                    any(Int::class.java),
-                    eq(smartActionsProvider),
-                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
-                )
-            )
-            .thenReturn(actions)
-
-        val quickShareAction =
-            saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!!
-
-        assertEquals("Action One", quickShareAction.title)
-        assertEquals(mutablePendingIntent, quickShareAction.actionIntent)
-    }
-
-    @Test
-    fun testCreateQuickShareAction_originalWasNull_returnsNull() {
-        val quickShareAction =
-            saveImageTask.createQuickShareAction(
-                null,
-                testScreenshotId,
-                testUri,
-                testImageTime,
-                testBitmap,
-                testUser
-            )
-
-        assertNull(quickShareAction)
-    }
-
-    @Test
-    fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() {
-        val actions = ArrayList<Notification.Action>()
-        actions.add(constructAction("New Test Action", immutablePendingIntent))
-        whenever(
-                smartActions.getSmartActions(
-                    eq(testScreenshotId),
-                    eq(smartActionsUriFuture),
-                    any(Int::class.java),
-                    eq(smartActionsProvider),
-                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
-                )
-            )
-            .thenReturn(actions)
-        val origAction = constructAction("Old Test Action", immutablePendingIntent)
-
-        val quickShareAction =
-            saveImageTask.createQuickShareAction(
-                origAction,
-                testScreenshotId,
-                testUri,
-                testImageTime,
-                testBitmap,
-                testUser,
-            )
-
-        assertNull(quickShareAction)
-    }
-
-    @Test
-    fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() {
-        val actions = ArrayList<Notification.Action>()
-        val action = constructAction("Action One", mutablePendingIntent)
-        actions.add(action)
-        whenever(
-                smartActions.getSmartActions(
-                    eq(testScreenshotId),
-                    eq(smartActionsUriFuture),
-                    any(Int::class.java),
-                    eq(smartActionsProvider),
-                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
-                )
-            )
-            .thenReturn(actions)
-
-        val quickShareAction =
-            saveImageTask.createQuickShareAction(
-                constructAction("Test Action", mutablePendingIntent),
-                testScreenshotId,
-                testUri,
-                testImageTime,
-                testBitmap,
-                testUser
-            )
-        val quickSharePendingIntent =
-            quickShareAction.actionIntent.intent.extras!!.getParcelable(
-                SmartActionsReceiver.EXTRA_ACTION_INTENT,
-                PendingIntent::class.java
-            )
-
-        assertEquals("Test Action", quickShareAction.title)
-        assertEquals(mutablePendingIntent, quickSharePendingIntent)
-    }
-
-    @Test
-    fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() {
-        val actions = ArrayList<Notification.Action>()
-        val action = constructAction("Test Action", immutablePendingIntent)
-        actions.add(action)
-        whenever(
-                smartActions.getSmartActions(
-                    eq(testScreenshotId),
-                    eq(smartActionsUriFuture),
-                    any(Int::class.java),
-                    eq(smartActionsProvider),
-                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
-                )
-            )
-            .thenReturn(actions)
-
-        val quickShareAction =
-            saveImageTask.createQuickShareAction(
-                constructAction("Test Action", immutablePendingIntent),
-                testScreenshotId,
-                testUri,
-                testImageTime,
-                testBitmap,
-                testUser,
-            )!!
-
-        assertEquals("Test Action", quickShareAction.title)
-        assertEquals(
-            immutablePendingIntent,
-            quickShareAction.actionIntent.intent.extras!!.getParcelable(
-                SmartActionsReceiver.EXTRA_ACTION_INTENT,
-                PendingIntent::class.java
-            )
-        )
-    }
-
-    private fun constructAction(title: String, intent: PendingIntent): Notification.Action {
-        return Notification.Action.Builder(testIcon, title, intent).build()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index ed99705..b177e4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -101,7 +101,7 @@
     private fun getAvalancheSuppressor() : AvalancheSuppressor {
         return AvalancheSuppressor(
             avalancheProvider, systemClock, settingsInteractor, packageManager,
-            uiEventLogger, context, notificationManager, logger
+            uiEventLogger, context, notificationManager, logger, systemSettings
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index b6e23c1..715e3b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -85,7 +85,7 @@
                 underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
             val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
 
             assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
             assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
@@ -104,7 +104,7 @@
                 underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
             val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
 
             assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
             assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
@@ -123,7 +123,7 @@
             wifiRepository.setIsWifiDefault(true)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 3,
                 )
@@ -143,7 +143,7 @@
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setIsWifiDefault(true)
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 3,
                 )
@@ -180,7 +180,7 @@
             val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID + 10,
                     level = 3,
                 )
@@ -201,7 +201,7 @@
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 3,
                 )
@@ -221,7 +221,7 @@
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 3,
                 )
@@ -240,7 +240,7 @@
             val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 1,
                     numberOfLevels = 6,
@@ -303,7 +303,7 @@
 
             whenever(telephonyManager.simOperatorName).thenReturn("New SIM name")
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     subscriptionId = SUB_ID,
                     level = 3,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index a03980a..fd23655 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -488,7 +488,7 @@
 
             // WHEN we set up carrier merged info
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 3,
                 )
@@ -499,7 +499,7 @@
 
             // WHEN we update the info
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 1,
                 )
@@ -538,7 +538,7 @@
 
             // WHEN isCarrierMerged is set to true
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 3,
                 )
@@ -550,7 +550,7 @@
 
             // WHEN the carrier merge network is updated
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 4,
                 )
@@ -602,7 +602,7 @@
 
             // THEN updates to the carrier merged level aren't logged
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 4,
                 )
@@ -610,7 +610,7 @@
             assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged(
+                WifiNetworkModel.CarrierMerged.of(
                     SUB_ID,
                     level = 3,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index a1cb29b..c0a206a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -574,7 +574,7 @@
             val latest by collectLastValue(underTest.isWifiActive)
 
             // WHEN wifi is active
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
 
             // THEN the interactor returns true due to the wifi network being active
             assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index c1abf98..e7e4969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -375,7 +375,7 @@
             repo.isSatelliteProvisioned.value = true
 
             // GIVEN wifi network is active
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
 
             // THEN icon is null because the device is connected to wifi
             assertThat(latest).isNull()
@@ -573,7 +573,7 @@
             repo.isSatelliteProvisioned.value = true
 
             // GIVEN wifi network is active
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1))
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
 
             // THEN carrier text is null because the device is connected to wifi
             assertThat(latest).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index fed3317..975e2ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -154,7 +154,7 @@
             val latest by collectLastValue(underTest.tileModel)
 
             val networkModel =
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 4,
                     ssid = "test ssid",
                 )
@@ -183,7 +183,7 @@
             val latest by collectLastValue(underTest.tileModel)
 
             val networkModel =
-                WifiNetworkModel.Active(
+                WifiNetworkModel.Active.of(
                     level = 4,
                     ssid = "test ssid",
                     hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
@@ -295,7 +295,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.tileModel)
 
-            val networkModel = WifiNetworkModel.Inactive
+            val networkModel = WifiNetworkModel.Inactive()
 
             connectivityRepository.setWifiConnected(validated = false)
             wifiRepository.setIsWifiDefault(true)
@@ -310,7 +310,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.tileModel)
 
-            val networkModel = WifiNetworkModel.Inactive
+            val networkModel = WifiNetworkModel.Inactive()
 
             connectivityRepository.setWifiConnected(validated = false)
             wifiRepository.setIsWifiDefault(true)
@@ -390,7 +390,7 @@
 
     private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
         val networkModel =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 level = 4,
                 ssid = "test ssid",
                 hotspotDeviceType = hotspot,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 46f34e8..fd4b77d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -295,7 +295,10 @@
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
 
-            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+            val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason
+            assertThat(inactiveReason).contains("level")
+            assertThat(inactiveReason).contains("$WIFI_LEVEL_UNREACHABLE")
         }
 
     @Test
@@ -311,7 +314,10 @@
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
 
-            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+            val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason
+            assertThat(inactiveReason).contains("level")
+            assertThat(inactiveReason).contains("${WIFI_LEVEL_MAX + 1}")
         }
 
     @Test
@@ -327,7 +333,10 @@
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
 
-            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+            val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason
+            assertThat(inactiveReason).contains("level")
+            assertThat(inactiveReason).contains("${WIFI_LEVEL_MIN - 1}")
         }
 
     @Test
@@ -530,6 +539,25 @@
         }
 
     @Test
+    fun wifiNetwork_carrierMergedButInvalidLevel_flowHasInvalid() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.subscriptionId).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+        }
+
+    @Test
     fun wifiNetwork_notValidated_networkNotValidated() =
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
@@ -571,7 +599,7 @@
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
 
-            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive())
         }
 
     @Test
@@ -587,7 +615,7 @@
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
-            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive())
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index 92860ef..1495519 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,59 +33,109 @@
 @RunWith(AndroidJUnit4::class)
 class WifiNetworkModelTest : SysuiTestCase() {
     @Test
-    fun active_levelsInValidRange_noException() {
+    fun active_levelsInValidRange_createsActive() {
         (MIN_VALID_LEVEL..MAX_VALID_LEVEL).forEach { level ->
-            WifiNetworkModel.Active(level = level)
-            // No assert, just need no crash
+            val result = WifiNetworkModel.Active.of(level = level)
+            assertThat(result).isInstanceOf(WifiNetworkModel.Active::class.java)
         }
     }
 
-    @Test(expected = IllegalArgumentException::class)
-    fun active_levelNegative_exceptionThrown() {
-        WifiNetworkModel.Active(level = MIN_VALID_LEVEL - 1)
+    fun active_levelTooLow_returnsInactive() {
+        val result = WifiNetworkModel.Active.of(level = MIN_VALID_LEVEL - 1)
+        assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java)
     }
 
     @Test(expected = IllegalArgumentException::class)
-    fun active_levelTooHigh_exceptionThrown() {
-        WifiNetworkModel.Active(level = MAX_VALID_LEVEL + 1)
+    fun active_levelTooLow_createdByCopy_exceptionThrown() {
+        val starting = WifiNetworkModel.Active.of(level = MIN_VALID_LEVEL)
+
+        (starting as WifiNetworkModel.Active).copy(level = MIN_VALID_LEVEL - 1)
+    }
+
+    fun active_levelTooHigh_returnsInactive() {
+        val result = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL + 1)
+
+        assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java)
     }
 
     @Test(expected = IllegalArgumentException::class)
-    fun carrierMerged_invalidSubId_exceptionThrown() {
-        WifiNetworkModel.CarrierMerged(INVALID_SUBSCRIPTION_ID, 1)
+    fun active_levelTooHigh_createdByCopy_exceptionThrown() {
+        val starting = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL)
+
+        (starting as WifiNetworkModel.Active).copy(level = MAX_VALID_LEVEL + 1)
+    }
+
+    fun active_levelUnreachable_returnsInactive() {
+        val result = WifiNetworkModel.Active.of(level = WIFI_LEVEL_UNREACHABLE)
+
+        assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun active_levelUnreachable_createdByCopy_exceptionThrown() {
+        val starting = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL)
+
+        (starting as WifiNetworkModel.Active).copy(level = WIFI_LEVEL_UNREACHABLE)
+    }
+
+    fun carrierMerged_invalidSubId_returnsInvalid() {
+        val result = WifiNetworkModel.CarrierMerged.of(INVALID_SUBSCRIPTION_ID, level = 1)
+
+        assertThat(result).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun carrierMerged_invalidSubId_createdByCopy_exceptionThrown() {
+        val starting = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1)
+
+        (starting as WifiNetworkModel.CarrierMerged).copy(subscriptionId = INVALID_SUBSCRIPTION_ID)
+    }
+
+    fun carrierMerged_levelUnreachable_returnsInvalid() {
+        val result =
+            WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = WIFI_LEVEL_UNREACHABLE)
+
+        assertThat(result).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun carrierMerged_levelUnreachable_createdByCopy_exceptionThrown() {
+        val starting = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1)
+
+        (starting as WifiNetworkModel.CarrierMerged).copy(level = WIFI_LEVEL_UNREACHABLE)
     }
 
     @Test
     fun active_hasValidSsid_nullSsid_false() {
         val network =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 level = MAX_VALID_LEVEL,
                 ssid = null,
             )
 
-        assertThat(network.hasValidSsid()).isFalse()
+        assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isFalse()
     }
 
     @Test
     fun active_hasValidSsid_unknownSsid_false() {
         val network =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 level = MAX_VALID_LEVEL,
                 ssid = UNKNOWN_SSID,
             )
 
-        assertThat(network.hasValidSsid()).isFalse()
+        assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isFalse()
     }
 
     @Test
     fun active_hasValidSsid_validSsid_true() {
         val network =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 level = MAX_VALID_LEVEL,
                 ssid = "FakeSsid",
             )
 
-        assertThat(network.hasValidSsid()).isTrue()
+        assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isTrue()
     }
 
     // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
@@ -93,14 +144,15 @@
     fun logDiffs_carrierMergedToInactive_resetsAllFields() {
         val logger = TestLogger()
         val prevVal =
-            WifiNetworkModel.CarrierMerged(
+            WifiNetworkModel.CarrierMerged.of(
                 subscriptionId = 3,
                 level = 1,
             )
 
-        WifiNetworkModel.Inactive.logDiffs(prevVal, logger)
+        WifiNetworkModel.Inactive(inactiveReason = "TestReason").logDiffs(prevVal, logger)
 
-        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+        assertThat(logger.changes)
+            .contains(Pair(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=TestReason]"))
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
         assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
@@ -110,12 +162,12 @@
     fun logDiffs_inactiveToCarrierMerged_logsAllFields() {
         val logger = TestLogger()
         val carrierMerged =
-            WifiNetworkModel.CarrierMerged(
+            WifiNetworkModel.CarrierMerged.of(
                 subscriptionId = 3,
                 level = 2,
             )
 
-        carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+        carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive(), logger)
 
         assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
         assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3"))
@@ -128,14 +180,14 @@
     fun logDiffs_inactiveToActive_logsAllActiveFields() {
         val logger = TestLogger()
         val activeNetwork =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 isValidated = true,
                 level = 3,
                 ssid = "Test SSID",
                 hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
             )
 
-        activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+        activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive(), logger)
 
         assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
@@ -148,11 +200,13 @@
     fun logDiffs_activeToInactive_resetsAllActiveFields() {
         val logger = TestLogger()
         val activeNetwork =
-            WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID")
+            WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID")
 
-        WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+        WifiNetworkModel.Inactive(inactiveReason = "TestReason")
+            .logDiffs(prevVal = activeNetwork, logger)
 
-        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+        assertThat(logger.changes)
+            .contains(Pair(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=TestReason]"))
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
         assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
@@ -163,14 +217,14 @@
     fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
         val logger = TestLogger()
         val activeNetwork =
-            WifiNetworkModel.Active(
+            WifiNetworkModel.Active.of(
                 isValidated = true,
                 level = 3,
                 ssid = "Test SSID",
                 hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO,
             )
         val prevVal =
-            WifiNetworkModel.CarrierMerged(
+            WifiNetworkModel.CarrierMerged.of(
                 subscriptionId = 3,
                 level = 1,
             )
@@ -188,9 +242,9 @@
     fun logDiffs_activeToCarrierMerged_logsAllFields() {
         val logger = TestLogger()
         val activeNetwork =
-            WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID")
+            WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID")
         val carrierMerged =
-            WifiNetworkModel.CarrierMerged(
+            WifiNetworkModel.CarrierMerged.of(
                 subscriptionId = 3,
                 level = 2,
             )
@@ -208,9 +262,9 @@
     fun logDiffs_activeChangesLevel_onlyLevelLogged() {
         val logger = TestLogger()
         val prevActiveNetwork =
-            WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID")
+            WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID")
         val newActiveNetwork =
-            WifiNetworkModel.Active(isValidated = true, level = 2, ssid = "Test SSID")
+            WifiNetworkModel.Active.of(isValidated = true, level = 2, ssid = "Test SSID")
 
         newActiveNetwork.logDiffs(prevActiveNetwork, logger)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index ff398f9..37c7a48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -195,7 +195,7 @@
     @Test
     fun isIconVisible_notEnabled_outputsFalse() {
         wifiRepository.setIsWifiEnabled(false)
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2))
 
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
@@ -210,7 +210,7 @@
     @Test
     fun isIconVisible_enabled_outputsTrue() {
         wifiRepository.setIsWifiEnabled(true)
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2))
 
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 82acb40..96a0194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -206,51 +206,51 @@
                 // Enabled = false => no networks shown
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1),
+                    network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1),
                     expected = null,
                 ),
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected = null,
                 ),
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 1),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 1),
                     expected = null,
                 ),
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 3),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 3),
                     expected = null,
                 ),
 
                 // forceHidden = true => no networks shown
                 TestCase(
                     forceHidden = true,
-                    network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1),
+                    network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1),
                     expected = null,
                 ),
                 TestCase(
                     forceHidden = true,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected = null,
                 ),
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 2),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 2),
                     expected = null,
                 ),
                 TestCase(
                     forceHidden = true,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 1),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 1),
                     expected = null,
                 ),
 
                 // alwaysShowIconWhenEnabled = true => all Inactive and Active networks shown
                 TestCase(
                     alwaysShowIconWhenEnabled = true,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_NETWORK,
@@ -263,7 +263,7 @@
                 ),
                 TestCase(
                     alwaysShowIconWhenEnabled = true,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 4),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 4),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_INTERNET_ICONS[4],
@@ -276,7 +276,7 @@
                 ),
                 TestCase(
                     alwaysShowIconWhenEnabled = true,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 2),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 2),
                     expected =
                         Expected(
                             iconResource = WIFI_FULL_ICONS[2],
@@ -290,7 +290,7 @@
                 // hasDataCapabilities = false => all Inactive and Active networks shown
                 TestCase(
                     hasDataCapabilities = false,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_NETWORK,
@@ -303,7 +303,7 @@
                 ),
                 TestCase(
                     hasDataCapabilities = false,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 2),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 2),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_INTERNET_ICONS[2],
@@ -316,7 +316,7 @@
                 ),
                 TestCase(
                     hasDataCapabilities = false,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 0),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 0),
                     expected =
                         Expected(
                             iconResource = WIFI_FULL_ICONS[0],
@@ -330,7 +330,7 @@
                 // isDefault = true => all Inactive and Active networks shown
                 TestCase(
                     isDefault = true,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_NETWORK,
@@ -343,7 +343,7 @@
                 ),
                 TestCase(
                     isDefault = true,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 3),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 3),
                     expected =
                         Expected(
                             iconResource = WIFI_NO_INTERNET_ICONS[3],
@@ -356,7 +356,7 @@
                 ),
                 TestCase(
                     isDefault = true,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 1),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 1),
                     expected =
                         Expected(
                             iconResource = WIFI_FULL_ICONS[1],
@@ -372,14 +372,14 @@
                     enabled = true,
                     isDefault = true,
                     forceHidden = false,
-                    network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1),
+                    network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1),
                     expected = null,
                 ),
 
                 // isDefault = false => no networks shown
                 TestCase(
                     isDefault = false,
-                    network = WifiNetworkModel.Inactive,
+                    network = WifiNetworkModel.Inactive(),
                     expected = null,
                 ),
                 TestCase(
@@ -389,7 +389,7 @@
                 ),
                 TestCase(
                     isDefault = false,
-                    network = WifiNetworkModel.Active(isValidated = false, level = 3),
+                    network = WifiNetworkModel.Active.of(isValidated = false, level = 3),
                     expected = null,
                 ),
 
@@ -397,7 +397,7 @@
                 // because wifi isn't the default connection (b/272509965).
                 TestCase(
                     isDefault = false,
-                    network = WifiNetworkModel.Active(isValidated = true, level = 4),
+                    network = WifiNetworkModel.Active.of(isValidated = true, level = 4),
                     expected = null,
                 ),
             )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/TestScopeProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
rename to packages/SystemUI/tests/utils/src/com/android/keyguard/TestScopeProvider.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorKosmos.kt
new file mode 100644
index 0000000..15c7e25
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.deviceEntrySideFpsOverlayInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.sideFpsOverlayInteractor by Fixture {
+    SideFpsOverlayInteractorImpl(
+        biometricStatusInteractor,
+        displayStateInteractor,
+        deviceEntrySideFpsOverlayInteractor,
+        sideFpsSensorInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt
index 79d58a1..59809e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt
@@ -19,27 +19,19 @@
 import android.content.applicationContext
 import android.view.layoutInflater
 import android.view.windowManager
-import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
-import com.android.systemui.keyguard.domain.interactor.deviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel
+import com.android.systemui.biometrics.domain.interactor.sideFpsOverlayInteractor
+import com.android.systemui.biometrics.ui.viewmodel.sideFpsOverlayViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.sideFpsOverlayViewBinder by Fixture {
     SideFpsOverlayViewBinder(
-        applicationScope = applicationCoroutineScope,
-        applicationContext = applicationContext,
-        { biometricStatusInteractor },
-        { displayStateInteractor },
-        { deviceEntrySideFpsOverlayInteractor },
+        applicationCoroutineScope,
+        applicationContext,
         { layoutInflater },
-        { sideFpsProgressBarViewModel },
-        { sideFpsSensorInteractor },
+        { sideFpsOverlayInteractor },
+        { sideFpsOverlayViewModel },
         { windowManager }
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt
index de03855..e10b2dd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt
@@ -27,9 +27,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.sideFpsOverlayViewModel by Fixture {
     SideFpsOverlayViewModel(
-        applicationContext = applicationContext,
-        deviceEntrySideFpsOverlayInteractor = deviceEntrySideFpsOverlayInteractor,
-        displayStateInteractor = displayStateInteractor,
-        sfpsSensorInteractor = sideFpsSensorInteractor,
+        applicationContext,
+        deviceEntrySideFpsOverlayInteractor,
+        displayStateInteractor,
+        sideFpsSensorInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 811c653..251a6b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -50,6 +50,13 @@
     Kosmos.Fixture {
         KeyboardTouchpadEduStatsInteractorImpl(
             backgroundScope = testScope.backgroundScope,
-            contextualEducationInteractor = contextualEducationInteractor
+            contextualEducationInteractor = contextualEducationInteractor,
+            inputDeviceRepository =
+                UserInputDeviceRepository(
+                    testDispatcher,
+                    keyboardRepository,
+                    touchpadRepository,
+                    userRepository
+                )
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 27eadb1..d920c4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -35,11 +36,13 @@
             transitionInteractor = keyguardTransitionInteractor,
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
-            sceneInteractor = sceneInteractor,
-            deviceEntryInteractor = deviceEntryInteractor,
-            quickSettingsSceneFamilyResolver = quickSettingsSceneFamilyResolver,
-            notifShadeSceneFamilyResolver = notifShadeSceneFamilyResolver,
+            sceneInteractor = { sceneInteractor },
+            deviceEntryInteractor = { deviceEntryInteractor },
+            quickSettingsSceneFamilyResolver = { quickSettingsSceneFamilyResolver },
+            notifShadeSceneFamilyResolver = { notifShadeSceneFamilyResolver },
             powerInteractor = powerInteractor,
             alternateBouncerInteractor = alternateBouncerInteractor,
+            keyguardInteractor = { keyguardInteractor },
+            shadeInteractor = { shadeInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 457bd28..c60305e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -61,6 +61,7 @@
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 import com.android.systemui.shade.shadeController
 import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
@@ -156,4 +157,5 @@
     val scrimStartable by lazy { kosmos.scrimStartable }
     val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor }
     val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
+    val shadeModeInteractor by lazy { kosmos.shadeModeInteractor }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
new file mode 100644
index 0000000..76d71dd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+var Kosmos.mediaDeviceLogger by
+    Kosmos.Fixture { MediaDeviceLogger(logcatLogBuffer("MediaDeviceLoggerKosmos")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index c479ce6..11408d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -41,5 +41,6 @@
             },
             fgExecutor = fakeExecutor,
             bgExecutor = fakeExecutor,
+            logger = mediaDeviceLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
index b03542c..33227a4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import android.content.applicationContext
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
@@ -33,6 +35,8 @@
             currentTilesInteractor,
             tilesAvailabilityInteractor,
             minimumTilesInteractor,
+            configurationInteractor,
+            applicationContext,
             infiniteGridLayout,
             applicationCoroutineScope,
             gridLayoutTypeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index dceb8bf..f66125a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.instanceIdSequenceFake
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
@@ -47,6 +48,7 @@
                         tileSpec,
                         QSTileUIConfig.Empty,
                         instanceIdSequenceFake.newInstanceId(),
+                        category = TileCategory.PROVIDED_BY_APP,
                     )
                 object : QSTileViewModel {
                     override val state: StateFlow<QSTileState?> =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
index 2a0ee88..73d9b32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
@@ -18,6 +18,7 @@
 
 import com.android.internal.logging.InstanceId
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
 
 object QSTileConfigTestBuilder {
 
@@ -30,12 +31,14 @@
         var instanceId: InstanceId = InstanceId.fakeInstanceId(0)
         var metricsSpec: String = tileSpec.spec
         var policy: QSTilePolicy = QSTilePolicy.NoRestrictions
+        var category: TileCategory = TileCategory.UNKNOWN
 
         fun build() =
             QSTileConfig(
                 tileSpec,
                 uiConfig,
                 instanceId,
+                category,
                 metricsSpec,
                 policy,
             )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index ae33aea..d17b575 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.sceneFamilyResolvers: Map<SceneKey, SceneResolver>
@@ -48,7 +48,7 @@
     Kosmos.Fixture {
         NotifShadeSceneFamilyResolver(
             applicationScope = applicationCoroutineScope,
-            shadeInteractor = shadeInteractor,
+            shadeModeInteractor = shadeModeInteractor,
         )
     }
 
@@ -56,6 +56,6 @@
     Kosmos.Fixture {
         QuickSettingsSceneFamilyResolver(
             applicationScope = applicationCoroutineScope,
-            shadeInteractor = shadeInteractor,
+            shadeModeInteractor = shadeModeInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 54208b9..04d930c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -53,7 +53,7 @@
             scope = applicationCoroutineScope,
             keyguardRepository = keyguardRepository,
             sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
-            repository = shadeRepository
+            repository = shadeRepository,
         )
     }
 var Kosmos.shadeInteractor: ShadeInteractor by Kosmos.Fixture { shadeInteractorImpl }
@@ -70,6 +70,6 @@
             userSetupRepository = userSetupRepository,
             userSwitcherInteractor = userSwitcherInteractor,
             baseShadeInteractor = baseShadeInteractor,
-            shadeRepository = shadeRepository,
+            shadeModeInteractor = shadeModeInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
new file mode 100644
index 0000000..7892e96
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+
+val Kosmos.shadeModeInteractor by Fixture {
+    ShadeModeInteractorImpl(
+        applicationScope = applicationCoroutineScope,
+        repository = shadeRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationEntryHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationEntryHelper.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 709be5e..7ca90ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -32,7 +32,7 @@
     override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
 
     private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
-        MutableStateFlow(WifiNetworkModel.Inactive)
+        MutableStateFlow(WifiNetworkModel.Inactive())
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
     override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 888351f..ba6ffd7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -33,11 +33,7 @@
 
 class FakeAudioRepository : AudioRepository {
 
-    private val unMutableStreams =
-        setOf(
-            AudioManager.STREAM_VOICE_CALL,
-            AudioManager.STREAM_ALARM,
-        )
+    private val unMutableStreams = setOf(AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_ALARM)
 
     private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
     override val mode: StateFlow<Int> = mutableMode.asStateFlow()
@@ -126,7 +122,7 @@
         lastAudibleVolumes[audioStream] = volume
     }
 
-    override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
+    override suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode) {
         mutableRingerMode.value = mode
     }
 
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 26b0f61..136738f 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -22,6 +22,7 @@
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraExtensionCharacteristics;
@@ -2525,6 +2526,19 @@
         }
 
         @Override
+        public SyncFence getFence() {
+            if (mParcelImage.fence != null) {
+                try {
+                    return SyncFence.create(mParcelImage.fence.dup());
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to parcel buffer fence!");
+                }
+            }
+
+            return SyncFence.createEmpty();
+        }
+
+        @Override
         protected final void finalize() throws Throwable {
             try {
                 close();
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 469759b..7f9d9c2 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -142,6 +142,10 @@
       "host": true
     },
     {
+      "name": "RavenwoodCoreTest",
+      "host": true
+    },
+    {
       "name": "RavenwoodMinimumTest",
       "host": true
     },
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
new file mode 100644
index 0000000..a5a16c1
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.ravenwood.RavenwoodConfig;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to make sure the config field is used.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodConfigTest {
+    private static final String PACKAGE_NAME = "com.test";
+
+    @RavenwoodConfig.Config
+    public static RavenwoodConfig sConfig =
+            new RavenwoodConfig.Builder()
+                    .setPackageName(PACKAGE_NAME)
+                    .build();
+
+    @Test
+    public void testConfig() {
+        assumeTrue(isOnRavenwood());
+        assertEquals(PACKAGE_NAME,
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
new file mode 100644
index 0000000..c25d2b4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure having multiple RavenwoodRule's is detected.
+ * (But only when running on ravenwod. Otherwise it'll be ignored.)
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodMultipleRuleTest {
+
+    @Rule(order = Integer.MIN_VALUE)
+    public final ExpectedException mExpectedException = ExpectedException.none();
+
+    @Rule
+    public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
+
+    @Rule
+    public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
+
+    public RavenwoodMultipleRuleTest() {
+        // We can't call it within the test method because the exception happens before
+        // calling the method, so set it up here.
+        if (RavenwoodConfig.isOnRavenwood()) {
+            mExpectedException.expectMessage("Multiple nesting RavenwoodRule");
+        }
+    }
+
+    @Test
+    public void testMultipleRulesNotAllowed() {
+        Assume.assumeTrue(RavenwoodConfig.isOnRavenwood());
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java
deleted file mode 100644
index f9794ad..0000000
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest.listenertests;
-
-import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
-
-import org.junit.AfterClass;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-/**
- * Test that throws from @AfterClass.
- *
- * Tradefed would ignore it, so instead RavenwoodAwareTestRunner would detect it and kill
- * the self (test) process.
- *
- * Unfortunately, this behavior can't easily be tested from within this class, so for now
- * it's only used for a manual test, which you can run by removing the @Ignore.
- *
- * TODO(b/364948126) Improve the tests and automate it.
- */
-@Ignore
-@RunWith(ParameterizedAndroidJunit4.class)
-public class RavenwoodAfterClassFailureTest {
-    public RavenwoodAfterClassFailureTest(String param) {
-    }
-
-    @AfterClass
-    public static void afterClass() {
-        if (!isOnRavenwood()) return; // Don't do anything on real device.
-
-        throw new RuntimeException("FAILURE");
-    }
-
-    @Parameters
-    public static List<String> getParams() {
-        var params =  new ArrayList<String>();
-        params.add("foo");
-        params.add("bar");
-        return params;
-    }
-
-    @Test
-    public void test1() {
-    }
-
-    @Test
-    public void test2() {
-    }
-}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java
deleted file mode 100644
index 61fb068..0000000
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest.listenertests;
-
-import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
-
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-/**
- * Test that fails in assumption in @BeforeClass.
- *
- * This is only used for manual tests. Make sure `atest` shows 4 test results with
- * "ASSUMPTION_FAILED".
- *
- * TODO(b/364948126) Improve the tests and automate it.
- */
-@RunWith(ParameterizedAndroidJunit4.class)
-public class RavenwoodBeforeClassAssumptionFailureTest {
-    public RavenwoodBeforeClassAssumptionFailureTest(String param) {
-    }
-
-    @BeforeClass
-    public static void beforeClass() {
-        if (!isOnRavenwood()) return; // Don't do anything on real device.
-
-        Assume.assumeTrue(false);
-    }
-
-    @Parameters
-    public static List<String> getParams() {
-        var params =  new ArrayList<String>();
-        params.add("foo");
-        params.add("bar");
-        return params;
-    }
-
-    @Test
-    public void test1() {
-    }
-
-    @Test
-    public void test2() {
-    }
-}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java
deleted file mode 100644
index 626ce81..0000000
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest.listenertests;
-
-import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
-
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-/**
- * Test that fails throws from @BeforeClass.
- *
- * This is only used for manual tests. Make sure `atest` shows 4 test results with
- * a "FAILURE" runtime exception.
- *
- * In order to run the test, you'll need to remove the @Ignore.
- *
- * TODO(b/364948126) Improve the tests and automate it.
- */
-@Ignore
-@RunWith(ParameterizedAndroidJunit4.class)
-public class RavenwoodBeforeClassFailureTest {
-    public static final String TAG = "RavenwoodBeforeClassFailureTest";
-
-    public RavenwoodBeforeClassFailureTest(String param) {
-    }
-
-    @BeforeClass
-    public static void beforeClass() {
-        if (!isOnRavenwood()) return; // Don't do anything on real device.
-
-        throw new RuntimeException("FAILURE");
-    }
-
-    @Parameters
-    public static List<String> getParams() {
-        var params =  new ArrayList<String>();
-        params.add("foo");
-        params.add("bar");
-        return params;
-    }
-
-    @Test
-    public void test1() {
-    }
-
-    @Test
-    public void test2() {
-    }
-}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java
deleted file mode 100644
index dc949c4..0000000
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest.listenertests;
-
-import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
-
-import static org.junit.Assume.assumeTrue;
-
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-/**
- * Test that fails in assumption from a class rule.
- *
- * This is only used for manual tests. Make sure `atest` shows 4 test results with
- * "ASSUMPTION_FAILED".
- *
- * TODO(b/364948126) Improve the tests and automate it.
- */
-@RunWith(ParameterizedAndroidJunit4.class)
-public class RavenwoodClassRuleAssumptionFailureTest {
-    public static final String TAG = "RavenwoodClassRuleFailureTest";
-
-    @ClassRule
-    public static final TestRule sClassRule = new TestRule() {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            if (!isOnRavenwood()) {
-                return base; // Just run the test as-is on a real device.
-            }
-
-            assumeTrue(false);
-            return null; // unreachable
-        }
-    };
-
-    public RavenwoodClassRuleAssumptionFailureTest(String param) {
-    }
-
-    @Parameters
-    public static List<String> getParams() {
-        var params =  new ArrayList<String>();
-        params.add("foo");
-        params.add("bar");
-        return params;
-    }
-
-    @Test
-    public void test1() {
-    }
-
-    @Test
-    public void test2() {
-    }
-}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java
deleted file mode 100644
index 9996bec..0000000
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest.listenertests;
-
-import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
-
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-/**
- * Test that fails throws from a class rule.
- *
- * This is only used for manual tests. Make sure `atest` shows 4 test results with
- * a "FAILURE" runtime exception.
- *
- * In order to run the test, you'll need to remove the @Ignore.
- *
- * TODO(b/364948126) Improve the tests and automate it.
- */
-@Ignore
-@RunWith(ParameterizedAndroidJunit4.class)
-public class RavenwoodClassRuleFailureTest {
-    public static final String TAG = "RavenwoodClassRuleFailureTest";
-
-    @ClassRule
-    public static final TestRule sClassRule = new TestRule() {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            if (!isOnRavenwood()) {
-                return base; // Just run the test as-is on a real device.
-            }
-
-            throw new RuntimeException("FAILURE");
-        }
-    };
-
-    public RavenwoodClassRuleFailureTest(String param) {
-    }
-
-    @Parameters
-    public static List<String> getParams() {
-        var params =  new ArrayList<String>();
-        params.add("foo");
-        params.add("bar");
-        return params;
-    }
-
-    @Test
-    public void test1() {
-    }
-
-    @Test
-    public void test2() {
-    }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 6d21e440..92a1cb7 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -31,13 +31,16 @@
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 
 import org.junit.runner.Description;
-import org.junit.runner.Runner;
 import org.junit.runners.model.TestClass;
 
-import java.util.Stack;
-
 /**
  * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ *
+ * States are associated with each {@link RavenwoodAwareTestRunner} are stored in
+ * {@link RavenwoodRunnerState}, rather than as members of {@link RavenwoodAwareTestRunner}.
+ * See its javadoc for the reasons.
+ *
+ * All methods in this class must be called from the test main thread.
  */
 public class RavenwoodAwareTestRunnerHook {
     private static final String TAG = RavenwoodAwareTestRunner.TAG;
@@ -45,27 +48,10 @@
     private RavenwoodAwareTestRunnerHook() {
     }
 
-    private static RavenwoodTestStats sStats; // lazy initialization.
-
-    // Keep track of the current class description.
-
-    // Test classes can be nested because of "Suite", so we need a stack to keep track.
-    private static final Stack<Description> sClassDescriptions = new Stack<>();
-    private static Description sCurrentClassDescription;
-
-    private static RavenwoodTestStats getStats() {
-        if (sStats == null) {
-            // We don't want to throw in the static initializer, because tradefed may not report
-            // it properly, so we initialize it here.
-            sStats = new RavenwoodTestStats();
-        }
-        return sStats;
-    }
-
     /**
      * Called when a runner starts, before the inner runner gets a chance to run.
      */
-    public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+    public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
         // TODO: Move the initialization code to a better place.
 
         initOnce();
@@ -103,7 +89,29 @@
      */
     public static void onClassSkipped(Description description) {
         Log.i(TAG, "onClassSkipped: description=" + description);
-        getStats().onClassSkipped(description);
+        RavenwoodTestStats.getInstance().onClassSkipped(description);
+    }
+
+    /**
+     * Called before the inner runner starts.
+     */
+    public static void onBeforeInnerRunnerStart(
+            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
+        Log.v(TAG, "onBeforeInnerRunnerStart: description=" + description);
+
+        // Prepare the environment before the inner runner starts.
+        RavenwoodRunnerState.forRunner(runner).enterTestClass(description);
+    }
+
+    /**
+     * Called after the inner runner finished.
+     */
+    public static void onAfterInnerRunnerFinished(
+            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
+        Log.v(TAG, "onAfterInnerRunnerFinished: description=" + description);
+
+        RavenwoodTestStats.getInstance().onClassFinished(description);
+        RavenwoodRunnerState.forRunner(runner).exitTestClass();
     }
 
     /**
@@ -112,21 +120,23 @@
      * Return false if it should be skipped.
      */
     public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order) {
+            Scope scope, Order order) throws Throwable {
         Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
 
-        if (scope == Scope.Class && order == Order.Outer) {
-            // Keep track of the current class.
-            sCurrentClassDescription = description;
-            sClassDescriptions.push(description);
+        if (scope == Scope.Instance && order == Order.Outer) {
+            // Start of a test method.
+            RavenwoodRunnerState.forRunner(runner).enterTestMethod(description);
         }
 
+        final var classDescription = RavenwoodRunnerState.forRunner(runner).getClassDescription();
+
         // Class-level annotations are checked by the runner already, so we only check
         // method-level annotations here.
         if (scope == Scope.Instance && order == Order.Outer) {
             if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
                     description, true)) {
-                getStats().onTestFinished(sCurrentClassDescription, description, Result.Skipped);
+                RavenwoodTestStats.getInstance().onTestFinished(
+                        classDescription, description, Result.Skipped);
                 return false;
             }
         }
@@ -142,15 +152,13 @@
             Scope scope, Order order, Throwable th) {
         Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
 
-        if (scope == Scope.Instance && order == Order.Outer) {
-            getStats().onTestFinished(sCurrentClassDescription, description,
-                    th == null ? Result.Passed : Result.Failed);
+        final var classDescription = RavenwoodRunnerState.forRunner(runner).getClassDescription();
 
-        } else if (scope == Scope.Class && order == Order.Outer) {
-            getStats().onClassFinished(sCurrentClassDescription);
-            sClassDescriptions.pop();
-            sCurrentClassDescription =
-                    sClassDescriptions.size() == 0 ? null : sClassDescriptions.peek();
+        if (scope == Scope.Instance && order == Order.Outer) {
+            // End of a test method.
+            RavenwoodRunnerState.forRunner(runner).exitTestMethod();
+            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description,
+                    th == null ? Result.Passed : Result.Failed);
         }
 
         // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
@@ -190,4 +198,25 @@
     public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
         return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true);
     }
-}
+
+    /**
+     * Called by RavenwoodRule.
+     */
+    public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
+            Description description, RavenwoodRule rule) throws Throwable {
+        Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
+
+        RavenwoodRunnerState.forRunner(runner).enterRavenwoodRule(rule);
+    }
+
+
+    /**
+     * Called by RavenwoodRule.
+     */
+    public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
+            Description description, RavenwoodRule rule) throws Throwable {
+        Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
+
+        RavenwoodRunnerState.forRunner(runner).exitRavenwoodRule(rule);
+    }
+}
\ No newline at end of file
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
new file mode 100644
index 0000000..d73afd4
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMember;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.ravenwood.common.RavenwoodRuntimeException;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.WeakHashMap;
+
+/**
+ * Used to store various states associated with the current test runner.
+ *
+ * This class could be added to {@link RavenwoodAwareTestRunner} as a field, but we don't
+ * want to put it in junit-src/ (for one, that'll cause all the downstream dependencies to be
+ * rebuilt when this file is updated), so we manage it separately using a Map from each
+ * {@link RavenwoodAwareTestRunner} instance to a {@link RavenwoodRunnerState}.
+ *
+ * All members must be called from the runner's main thread.
+ */
+public final class RavenwoodRunnerState {
+    private static final String TAG = "RavenwoodRunnerState";
+
+    @GuardedBy("sStates")
+    private static final WeakHashMap<RavenwoodAwareTestRunner, RavenwoodRunnerState> sStates =
+            new WeakHashMap<>();
+
+    /**
+     * Get the instance for a given runner.
+     */
+    public static RavenwoodRunnerState forRunner(@NonNull RavenwoodAwareTestRunner runner) {
+        synchronized (sStates) {
+            var ret = sStates.get(runner);
+            if (ret == null) {
+                ret = new RavenwoodRunnerState(runner);
+                sStates.put(runner, ret);
+            }
+            return ret;
+        }
+    }
+
+    private final RavenwoodAwareTestRunner mRunner;
+
+    private RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
+        mRunner = runner;
+    }
+
+    private Description mClassDescription;
+    private Description mMethodDescription;
+
+    private RavenwoodConfig mCurrentConfig;
+    private RavenwoodRule mCurrentRule;
+
+    public Description getClassDescription() {
+        return mClassDescription;
+    }
+
+    public void enterTestClass(Description classDescription) throws IOException {
+        mClassDescription = classDescription;
+
+        mCurrentConfig = extractConfiguration(mRunner.getTestClass().getJavaClass());
+
+        if (mCurrentConfig != null) {
+            RavenwoodRuntimeEnvironmentController.init(mCurrentConfig);
+        }
+    }
+
+    public void exitTestClass() {
+        if (mCurrentConfig != null) {
+            try {
+                RavenwoodRuntimeEnvironmentController.reset();
+            } finally {
+                mClassDescription = null;
+            }
+        }
+    }
+
+    public void enterTestMethod(Description description) {
+        mMethodDescription = description;
+    }
+
+    public void exitTestMethod() {
+        mMethodDescription = null;
+    }
+
+    public void enterRavenwoodRule(RavenwoodRule rule) throws IOException {
+        if (mCurrentConfig != null) {
+            fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
+                    + " Suggest migrating to RavenwoodConfiguration.");
+        }
+        if (mCurrentRule != null) {
+            fail("Multiple nesting RavenwoodRule's are detected in the same class,"
+                    + " which is not supported.");
+        }
+        mCurrentRule = rule;
+        RavenwoodRuntimeEnvironmentController.init(rule.getConfiguration());
+    }
+
+    public void exitRavenwoodRule(RavenwoodRule rule) {
+        if (mCurrentRule != rule) {
+            return; // This happens if the rule did _not_ take effect somehow.
+        }
+
+        try {
+            RavenwoodRuntimeEnvironmentController.reset();
+        } finally {
+            mCurrentRule = null;
+        }
+    }
+
+    /**
+     * @return a configuration from a test class, if any.
+     */
+    @Nullable
+    private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
+        final boolean hasRavenwoodRule = hasRavenwoodRule(testClass);
+
+        var field = findConfigurationField(testClass);
+        if (field == null) {
+            return null;
+        }
+        if (hasRavenwoodRule) {
+            fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
+                    + " Suggest migrating to RavenwoodConfiguration.");
+        }
+
+        try {
+            return (RavenwoodConfig) field.get(null);
+        } catch (IllegalAccessException e) {
+            throw new RavenwoodRuntimeException("Failed to fetch from the configuration field", e);
+        }
+    }
+
+    /**
+     * @return true if the current target class (or its super classes) has any @Rule / @ClassRule
+     * fields of type RavenwoodRule.
+     *
+     * Note, this check won't detect cases where a Rule is of type
+     * {@link TestRule} and still be a {@link RavenwoodRule}. But that'll be detected at runtime
+     * as a failure, in {@link #enterRavenwoodRule}.
+     */
+    private static boolean hasRavenwoodRule(Class<?> testClass) {
+        for (var field : testClass.getDeclaredFields()) {
+            if (!field.isAnnotationPresent(Rule.class)
+                    && field.isAnnotationPresent(ClassRule.class)) {
+                continue;
+            }
+            if (field.getType().equals(RavenwoodRule.class)) {
+                return true;
+            }
+        }
+        // JUnit supports rules as methods, so we need to check them too.
+        for (var method : testClass.getDeclaredMethods()) {
+            if (!method.isAnnotationPresent(Rule.class)
+                    && method.isAnnotationPresent(ClassRule.class)) {
+                continue;
+            }
+            if (method.getReturnType().equals(RavenwoodRule.class)) {
+                return true;
+            }
+        }
+        // Look into the super class.
+        if (!testClass.getSuperclass().equals(Object.class)) {
+            return hasRavenwoodRule(testClass.getSuperclass());
+        }
+        return false;
+    }
+
+    /**
+     * Find and return a field with @RavenwoodConfiguration.Config, which must be of type
+     * RavenwoodConfiguration.
+     */
+    @Nullable
+    private static Field findConfigurationField(Class<?> testClass) {
+        Field foundField = null;
+
+        for (var field : testClass.getDeclaredFields()) {
+            final var hasAnot = field.isAnnotationPresent(RavenwoodConfig.Config.class);
+            final var isType = field.getType().equals(RavenwoodConfig.class);
+
+            if (hasAnot) {
+                if (isType) {
+                    // Good, use this field.
+                    if (foundField != null) {
+                        fail(String.format(
+                                "Class %s has multiple fields with %s",
+                                testClass.getCanonicalName(),
+                                "@RavenwoodConfiguration.Config"));
+                    }
+                    // Make sure it's static public
+                    ensureIsPublicMember(field, true);
+
+                    foundField = field;
+                } else {
+                    fail(String.format(
+                            "Field %s.%s has %s but type is not %s",
+                            testClass.getCanonicalName(),
+                            field.getName(),
+                            "@RavenwoodConfiguration.Config",
+                            "RavenwoodConfiguration"));
+                    return null; // unreachable
+                }
+            } else {
+                if (isType) {
+                    fail(String.format(
+                            "Field %s.%s does not have %s but type is %s",
+                            testClass.getCanonicalName(),
+                            field.getName(),
+                            "@RavenwoodConfiguration.Config",
+                            "RavenwoodConfiguration"));
+                    return null; // unreachable
+                } else {
+                    // Unrelated field, ignore.
+                    continue;
+                }
+            }
+        }
+        if (foundField != null) {
+            return foundField;
+        }
+        if (!testClass.getSuperclass().equals(Object.class)) {
+            return findConfigurationField(testClass.getSuperclass());
+        }
+        return null;
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
similarity index 66%
rename from ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
rename to ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 0059360..03c9001 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -18,6 +18,7 @@
 
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -26,6 +27,7 @@
 import android.app.Instrumentation;
 import android.app.ResourcesManager;
 import android.content.res.Resources;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
@@ -36,6 +38,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.ravenwood.common.RavenwoodRuntimeException;
+import com.android.ravenwood.common.SneakyThrow;
 import com.android.server.LocalServices;
 
 import org.junit.runner.Description;
@@ -52,14 +56,25 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 
-public class RavenwoodRuleImpl {
+/**
+ * Responsible for initializing and de-initializing the environment, according to a
+ * {@link RavenwoodConfig}.
+ */
+public class RavenwoodRuntimeEnvironmentController {
+    private static final String TAG = "RavenwoodRuntimeEnvironmentController";
+
+    private RavenwoodRuntimeEnvironmentController() {
+    }
+
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
 
     /**
      * When enabled, attempt to dump all thread stacks just before we hit the
      * overall Tradefed timeout, to aid in debugging deadlocks.
      */
-    private static final boolean ENABLE_TIMEOUT_STACKS = false;
+    private static final boolean ENABLE_TIMEOUT_STACKS =
+            "1".equals(System.getenv("RAVENWOOD_ENABLE_TIMEOUT_STACKS"));
+
     private static final int TIMEOUT_MILLIS = 9_000;
 
     private static final ScheduledExecutorService sTimeoutExecutor =
@@ -67,10 +82,13 @@
 
     private static ScheduledFuture<?> sPendingTimeout;
 
+    private static long sOriginalIdentityToken = -1;
+
     /**
      * When enabled, attempt to detect uncaught exceptions from background threads.
      */
-    private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION = false;
+    private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION =
+            "1".equals(System.getenv("RAVENWOOD_ENABLE_UNCAUGHT_EXCEPTION_DETECTION"));
 
     /**
      * When set, an unhandled exception was discovered (typically on a background thread), and we
@@ -85,23 +103,59 @@
                 sPendingUncaughtException.compareAndSet(null, throwable);
             };
 
-    public static void init(RavenwoodRule rule) throws IOException {
+    // TODO: expose packCallingIdentity function in libbinder and use it directly
+    // See: packCallingIdentity in frameworks/native/libs/binder/IPCThreadState.cpp
+    private static long packBinderIdentityToken(
+            boolean hasExplicitIdentity, int callingUid, int callingPid) {
+        long res = ((long) callingUid << 32) | callingPid;
+        if (hasExplicitIdentity) {
+            res |= (0x1 << 30);
+        } else {
+            res &= ~(0x1 << 30);
+        }
+        return res;
+    }
+
+    private static RavenwoodConfig sConfig;
+
+    /**
+     * Initialize the environment.
+     */
+    public static void init(RavenwoodConfig config) throws IOException {
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            Log.i(TAG, "init() called here", new RuntimeException("STACKTRACE"));
+        }
+        try {
+            initInner(config);
+        } catch (Exception th) {
+            Log.e(TAG, "init() failed", th);
+            reset();
+            SneakyThrow.sneakyThrow(th);
+        }
+    }
+
+    private static void initInner(RavenwoodConfig config) throws IOException {
+        if (sConfig != null) {
+            throw new RavenwoodRuntimeException("Internal error: init() called without reset()");
+        }
+        sConfig = config;
         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
             maybeThrowPendingUncaughtException(false);
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
         }
 
-        android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
-        android.os.Binder.init$ravenwood();
-        setSystemProperties(rule.mSystemProperties);
+        android.os.Process.init$ravenwood(config.mUid, config.mPid);
+        sOriginalIdentityToken = Binder.clearCallingIdentity();
+        Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+        setSystemProperties(config.mSystemProperties);
 
         ServiceManager.init$ravenwood();
         LocalServices.removeAllServicesForTest();
 
-        ActivityManager.init$ravenwood(rule.mCurrentUser);
+        ActivityManager.init$ravenwood(config.mCurrentUser);
 
         final HandlerThread main;
-        if (rule.mProvideMainThread) {
+        if (config.mProvideMainThread) {
             main = new HandlerThread(MAIN_THREAD_NAME);
             main.start();
             Looper.setMainLooperForTest(main.getLooper());
@@ -125,21 +179,22 @@
                     emptyPaths, emptyPaths, emptyPaths,
                     emptyPaths, null, null,
                     new DisplayAdjustments().getCompatibilityInfo(),
-                    RavenwoodRuleImpl.class.getClassLoader(), null);
+                    RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
 
             assertNotNull(ret);
             return ret;
         };
 
-        rule.mContext = new RavenwoodContext(rule.mPackageName, main, resourcesSupplier);
-        rule.mInstrumentation = new Instrumentation();
-        rule.mInstrumentation.basicInit(rule.mContext);
-        InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
+        config.mContext = new RavenwoodContext(config.mPackageName, main, resourcesSupplier);
+        config.mInstrumentation = new Instrumentation();
+        config.mInstrumentation.basicInit(config.mContext);
+        InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
 
-        RavenwoodSystemServer.init(rule);
+        RavenwoodSystemServer.init(config);
 
         if (ENABLE_TIMEOUT_STACKS) {
-            sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
+            sPendingTimeout = sTimeoutExecutor.schedule(
+                    RavenwoodRuntimeEnvironmentController::dumpStacks,
                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
 
@@ -148,21 +203,33 @@
         Objects.requireNonNull(Build.VERSION.SDK);
     }
 
-    public static void reset(RavenwoodRule rule) {
+    /**
+     * De-initialize.
+     */
+    public static void reset() {
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+        }
+        if (sConfig == null) {
+            throw new RavenwoodRuntimeException("Internal error: reset() already called");
+        }
+        var config = sConfig;
+        sConfig = null;
+
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout.cancel(false);
         }
 
-        RavenwoodSystemServer.reset(rule);
+        RavenwoodSystemServer.reset(config);
 
         InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-        rule.mInstrumentation = null;
-        if (rule.mContext != null) {
-            ((RavenwoodContext) rule.mContext).cleanUp();
+        config.mInstrumentation = null;
+        if (config.mContext != null) {
+            ((RavenwoodContext) config.mContext).cleanUp();
         }
-        rule.mContext = null;
+        config.mContext = null;
 
-        if (rule.mProvideMainThread) {
+        if (config.mProvideMainThread) {
             Looper.getMainLooper().quit();
             Looper.clearMainLooperForTest();
         }
@@ -173,7 +240,7 @@
         ServiceManager.reset$ravenwood();
 
         setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
-        android.os.Binder.reset$ravenwood();
+        Binder.restoreCallingIdentity(sOriginalIdentityToken);
         android.os.Process.reset$ravenwood();
 
         ResourcesManager.setInstance(null); // Better structure needed.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index cd6b61d..f3a93c1 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -61,19 +61,19 @@
     private static TimingsTraceAndSlog sTimings;
     private static SystemServiceManager sServiceManager;
 
-    public static void init(RavenwoodRule rule) {
+    public static void init(RavenwoodConfig config) {
         // Avoid overhead if no services required
-        if (rule.mServicesRequired.isEmpty()) return;
+        if (config.mServicesRequired.isEmpty()) return;
 
         sStartedServices = new ArraySet<>();
         sTimings = new TimingsTraceAndSlog();
-        sServiceManager = new SystemServiceManager(rule.mContext);
+        sServiceManager = new SystemServiceManager(config.mContext);
         sServiceManager.setStartInfo(false,
                 SystemClock.elapsedRealtime(),
                 SystemClock.uptimeMillis());
         LocalServices.addService(SystemServiceManager.class, sServiceManager);
 
-        startServices(rule.mServicesRequired);
+        startServices(config.mServicesRequired);
         sServiceManager.sealStartedServices();
 
         // TODO: expand to include additional boot phases when relevant
@@ -81,7 +81,7 @@
         sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
     }
 
-    public static void reset(RavenwoodRule rule) {
+    public static void reset(RavenwoodConfig config) {
         // TODO: consider introducing shutdown boot phases
 
         LocalServices.removeServiceForTest(SystemServiceManager.class);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 3ffabef..428eb57 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -31,7 +31,7 @@
 import java.util.Map;
 
 /**
- * Creats a "stats" CSV file containing the test results.
+ * Collect test result stats and write them into a CSV file containing the test results.
  *
  * The output file is created as `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_[TIMESTAMP].csv`.
  * A symlink to the latest result will be created as
@@ -41,6 +41,21 @@
     private static final String TAG = "RavenwoodTestStats";
     private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
 
+    private static RavenwoodTestStats sInstance;
+
+    /**
+     * @return a singleton instance.
+     */
+    public static RavenwoodTestStats getInstance() {
+        if (sInstance == null) {
+            sInstance = new RavenwoodTestStats();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Represents a test result.
+     */
     public enum Result {
         Passed,
         Failed,
@@ -113,16 +128,25 @@
         });
     }
 
+    /**
+     * Call it when a test class is skipped.
+     */
     public void onClassSkipped(Description classDescription) {
         addResult(classDescription, Description.EMPTY, Result.Skipped);
         onClassFinished(classDescription);
     }
 
+    /**
+     * Call it when a test method is finished.
+     */
     public void onTestFinished(Description classDescription, Description testDescription,
             Result result) {
         addResult(classDescription, testDescription, result);
     }
 
+    /**
+     * Call it when a test class is finished.
+     */
     public void onClassFinished(Description classDescription) {
         int passed = 0;
         int skipped = 0;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index dffb263..bc944d7 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,8 +26,6 @@
 import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.ravenwood.common.SneakyThrow;
-
 import org.junit.Assume;
 import org.junit.AssumptionViolatedException;
 import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
@@ -61,6 +59,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Stack;
+import java.util.function.BiConsumer;
 
 /**
  * A test runner used for Ravenwood.
@@ -83,7 +82,7 @@
  * it will basically just delegate to the inner wrapper, and won't do anything special.
  * (no hooks, etc.)
  */
-public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
+public final class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
     public static final String TAG = "Ravenwood";
 
     @Inherited
@@ -105,7 +104,6 @@
 
     /** Scope of a hook. */
     public enum Scope {
-        Runner,
         Class,
         Instance,
     }
@@ -120,21 +118,21 @@
     private static class RavenwoodClassOuterRule implements TestRule {
         @Override
         public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Outer);
+            return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Outer);
         }
     }
 
     private static class RavenwoodClassInnerRule implements TestRule {
         @Override
         public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Inner);
+            return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Inner);
         }
     }
 
     private static class RavenwoodInstanceOuterRule implements TestRule {
         @Override
         public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().updateStatement(
+            return getCurrentRunner().wrapWithHooks(
                     base, description, Scope.Instance, Order.Outer);
         }
     }
@@ -142,7 +140,7 @@
     private static class RavenwoodInstanceInnerRule implements TestRule {
         @Override
         public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().updateStatement(
+            return getCurrentRunner().wrapWithHooks(
                     base, description, Scope.Instance, Order.Inner);
         }
     }
@@ -160,7 +158,7 @@
     /** Keeps track of the runner on the current thread. */
     private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
 
-    private static RavenwoodAwareTestRunner getCurrentRunner() {
+    static RavenwoodAwareTestRunner getCurrentRunner() {
         var runner = sCurrentRunner.get();
         if (runner == null) {
             throw new RuntimeException("Current test runner not set!");
@@ -296,7 +294,7 @@
 
     @Override
     public void run(RunNotifier realNotifier) {
-        final RunNotifier notifier = new RavenwoodRunNotifier(realNotifier);
+        final RavenwoodRunNotifier notifier = new RavenwoodRunNotifier(realNotifier);
 
         if (mRealRunner instanceof ClassSkippingTestRunner) {
             mRealRunner.run(notifier);
@@ -315,10 +313,26 @@
 
         sCurrentRunner.set(this);
         try {
-            runWithHooks(getDescription(), Scope.Runner, Order.Outer,
-                    () -> mRealRunner.run(notifier));
+            try {
+                RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart(
+                        this, getDescription());
+            } catch (Throwable th) {
+                notifier.reportBeforeTestFailure(getDescription(), th);
+                return;
+            }
+
+            // Delegate to the inner runner.
+            mRealRunner.run(notifier);
         } finally {
             sCurrentRunner.remove();
+
+            try {
+                RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished(
+                        this, getDescription());
+            } catch (Throwable th) {
+                notifier.reportAfterTestFailure(th);
+                return;
+            }
         }
     }
 
@@ -355,7 +369,7 @@
         }
     }
 
-    private Statement updateStatement(Statement base, Description description, Scope scope,
+    private Statement wrapWithHooks(Statement base, Description description, Scope scope,
             Order order) {
         if (!isOnRavenwood()) {
             return base;
@@ -368,7 +382,8 @@
         };
     }
 
-    private void runWithHooks(Description description, Scope scope, Order order, Runnable r) {
+    private void runWithHooks(Description description, Scope scope, Order order, Runnable r)
+            throws Throwable {
         runWithHooks(description, scope, order, new Statement() {
             @Override
             public void evaluate() throws Throwable {
@@ -377,7 +392,8 @@
         });
     }
 
-    private void runWithHooks(Description description, Scope scope, Order order, Statement s) {
+    private void runWithHooks(Description description, Scope scope, Order order, Statement s)
+            throws Throwable {
         if (isOnRavenwood()) {
             Assume.assumeTrue(
                     RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
@@ -394,7 +410,7 @@
                         this, description, scope, order, t);
             }
             if (shouldThrow) {
-                SneakyThrow.sneakyThrow(t);
+                throw t;
             }
         }
     }
@@ -615,16 +631,13 @@
                 return true;
             }
             if (mAfterTest) {
-                // Unfortunately, there's no good way to report it, so kill the own process.
-                onCriticalError(
-                        "Failures detected in @AfterClass, which would be swalloed by tradefed",
-                        th);
-                return true; // unreachable
+                reportAfterTestFailure(th);
+                return true;
             }
             return false;
         }
 
-        private void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
+        public void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
             // If a failure happens befere running any tests, we'll need to pretend
             // as if each test in the suite reported the failure, to work around b/364395552.
             for (var child : suiteDesc.getChildren()) {
@@ -646,11 +659,51 @@
                 }
             }
         }
+
+        public void reportAfterTestFailure(Throwable th) {
+            // Unfortunately, there's no good way to report it, so kill the own process.
+            onCriticalError(
+                    "Failures detected in @AfterClass, which would be swallowed by tradefed",
+                    th);
+        }
     }
 
+    private static volatile BiConsumer<String, Throwable> sCriticalErrorHanler;
+
     private void onCriticalError(@NonNull String message, @Nullable Throwable th) {
-        Log.e(TAG, "Critical error! Ravenwood cannot continue. Killing self process: "
-                + message, th);
+        Log.e(TAG, "Critical error! " + message, th);
+        var handler = sCriticalErrorHanler;
+        if (handler == null) {
+            handler = sDefaultCriticalErrorHandler;
+        }
+        handler.accept(message, th);
+    }
+
+    private static BiConsumer<String, Throwable> sDefaultCriticalErrorHandler = (message, th) -> {
+        Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th);
         System.exit(1);
+    };
+
+    /**
+     * Contains Ravenwood private APIs.
+     */
+    public static class RavenwoodPrivate {
+        private RavenwoodPrivate() {
+        }
+
+        /**
+         * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call
+         * System.exit().
+         */
+        public void setCriticalErrorHandler(
+                @Nullable BiConsumer<String, Throwable> handler) {
+            sCriticalErrorHanler = handler;
+        }
+    }
+
+    private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate();
+
+    public static RavenwoodPrivate private$ravenwood() {
+        return sRavenwoodPrivate;
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
new file mode 100644
index 0000000..04e0bed
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.SYSTEM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Instrumentation;
+import android.content.Context;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Represents how to configure the ravenwood environment for a test class.
+ *
+ * If a ravenwood test class has a public static field with the {@link Config} annotation,
+ * Ravenwood will extract the config from it and initializes the environment. The type of the
+ * field must be of {@link RavenwoodConfig}.
+ */
+public final class RavenwoodConfig {
+    /**
+     * Use this to mark a field as the configuration.
+     * @hide
+     */
+    @Target({ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Config {
+    }
+
+    private static final int NOBODY_UID = 9999;
+
+    private static final AtomicInteger sNextPid = new AtomicInteger(100);
+
+    int mCurrentUser = SYSTEM.getIdentifier();
+
+    /**
+     * Unless the test author requests differently, run as "nobody", and give each collection of
+     * tests its own unique PID.
+     */
+    int mUid = NOBODY_UID;
+    int mPid = sNextPid.getAndIncrement();
+
+    String mPackageName;
+
+    boolean mProvideMainThread = false;
+
+    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
+    final List<Class<?>> mServicesRequired = new ArrayList<>();
+
+    volatile Context mContext;
+    volatile Instrumentation mInstrumentation;
+
+    private RavenwoodConfig() {
+    }
+
+    /**
+     * Return if the current process is running on a Ravenwood test environment.
+     */
+    public static boolean isOnRavenwood() {
+        return RavenwoodRule.isOnRavenwood();
+    }
+
+    public static class Builder {
+        private final RavenwoodConfig mConfig = new RavenwoodConfig();
+
+        public Builder() {
+        }
+
+        /**
+         * Configure the identity of this process to be the system UID for the duration of the
+         * test. Has no effect on non-Ravenwood environments.
+         */
+        public Builder setProcessSystem() {
+            mConfig.mUid = SYSTEM_UID;
+            return this;
+        }
+
+        /**
+         * Configure the identity of this process to be an app UID for the duration of the
+         * test. Has no effect on non-Ravenwood environments.
+         */
+        public Builder setProcessApp() {
+            mConfig.mUid = FIRST_APPLICATION_UID;
+            return this;
+        }
+
+        /**
+         * Configure the identity of this process to be the given package name for the duration
+         * of the test. Has no effect on non-Ravenwood environments.
+         */
+        public Builder setPackageName(@NonNull String packageName) {
+            mConfig.mPackageName = Objects.requireNonNull(packageName);
+            return this;
+        }
+
+        /**
+         * Configure a "main" thread to be available for the duration of the test, as defined
+         * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
+         */
+        public Builder setProvideMainThread(boolean provideMainThread) {
+            mConfig.mProvideMainThread = provideMainThread;
+            return this;
+        }
+
+        /**
+         * Configure the given system property as immutable for the duration of the test.
+         * Read access to the key is allowed, and write access will fail. When {@code value} is
+         * {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect on non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyImmutable(@NonNull String key,
+                @Nullable Object value) {
+            mConfig.mSystemProperties.setValue(key, value);
+            mConfig.mSystemProperties.setAccessReadOnly(key);
+            return this;
+        }
+
+        /**
+         * Configure the given system property as mutable for the duration of the test.
+         * Both read and write access to the key is allowed, and its value will be reset between
+         * each test. When {@code value} is {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect on non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyMutable(@NonNull String key,
+                @Nullable Object value) {
+            mConfig.mSystemProperties.setValue(key, value);
+            mConfig.mSystemProperties.setAccessReadWrite(key);
+            return this;
+        }
+
+        /**
+         * Configure the set of system services that are required for this test to operate.
+         *
+         * For example, passing {@code android.hardware.SerialManager.class} as an argument will
+         * ensure that the underlying service is created, initialized, and ready to use for the
+         * duration of the test. The {@code SerialManager} instance can be obtained via
+         * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
+         * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+         */
+        public Builder setServicesRequired(@NonNull Class<?>... services) {
+            mConfig.mServicesRequired.clear();
+            for (Class<?> service : services) {
+                mConfig.mServicesRequired.add(service);
+            }
+            return this;
+        }
+
+        public RavenwoodConfig build() {
+            return mConfig;
+        }
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d569896..7847e7c 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,17 +16,13 @@
 
 package android.platform.test.ravenwood;
 
-import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.Process.SYSTEM_UID;
-import static android.os.UserHandle.SYSTEM;
-
 import static com.android.ravenwood.common.RavenwoodCommonUtils.log;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.annotations.EnabledOnRavenwood;
 
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 
@@ -34,26 +30,17 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Pattern;
 
 /**
- * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when
- * tests are run on non-Ravenwood test environments.
- *
- * This rule initializes and resets the Ravenwood environment between each test method to offer a
- * hermetic testing environment.
- *
- * By default, all tests are executed on Ravenwood, but annotations such as
- * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
- * and class level to "ignore" tests that may not be ready. When needed, a
- * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests
- * before a test class is fully initialized.
+ * @deprecated Use {@link RavenwoodConfig} to configure the ravenwood environment instead.
+ * A {@link RavenwoodRule} is no longer needed for {@link DisabledOnRavenwood}. To get the
+ * {@link Context} and {@link Instrumentation}, use
+ * {@link androidx.test.platform.app.InstrumentationRegistry} instead.
  */
-public class RavenwoodRule implements TestRule {
+@Deprecated
+public final class RavenwoodRule implements TestRule {
     private static final String TAG = "RavenwoodRule";
 
     static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
@@ -105,35 +92,19 @@
         }
     }
 
-    private static final int NOBODY_UID = 9999;
-
-    private static final AtomicInteger sNextPid = new AtomicInteger(100);
-
-    int mCurrentUser = SYSTEM.getIdentifier();
-
-    /**
-     * Unless the test author requests differently, run as "nobody", and give each collection of
-     * tests its own unique PID.
-     */
-    int mUid = NOBODY_UID;
-    int mPid = sNextPid.getAndIncrement();
-
-    String mPackageName;
-
-    boolean mProvideMainThread = false;
-
-    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
-
-    final List<Class<?>> mServicesRequired = new ArrayList<>();
-
-    volatile Context mContext;
-    volatile Instrumentation mInstrumentation;
+    private final RavenwoodConfig mConfiguration;
 
     public RavenwoodRule() {
+        mConfiguration = new RavenwoodConfig.Builder().build();
+    }
+
+    private RavenwoodRule(RavenwoodConfig config) {
+        mConfiguration = config;
     }
 
     public static class Builder {
-        private RavenwoodRule mRule = new RavenwoodRule();
+        private final RavenwoodConfig.Builder mBuilder =
+                new RavenwoodConfig.Builder();
 
         public Builder() {
         }
@@ -143,7 +114,7 @@
          * test. Has no effect on non-Ravenwood environments.
          */
         public Builder setProcessSystem() {
-            mRule.mUid = SYSTEM_UID;
+            mBuilder.setProcessSystem();
             return this;
         }
 
@@ -152,7 +123,7 @@
          * test. Has no effect on non-Ravenwood environments.
          */
         public Builder setProcessApp() {
-            mRule.mUid = FIRST_APPLICATION_UID;
+            mBuilder.setProcessApp();
             return this;
         }
 
@@ -160,8 +131,8 @@
          * Configure the identity of this process to be the given package name for the duration
          * of the test. Has no effect on non-Ravenwood environments.
          */
-        public Builder setPackageName(/* @NonNull */ String packageName) {
-            mRule.mPackageName = Objects.requireNonNull(packageName);
+        public Builder setPackageName(@NonNull String packageName) {
+            mBuilder.setPackageName(packageName);
             return this;
         }
 
@@ -170,7 +141,7 @@
          * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
          */
         public Builder setProvideMainThread(boolean provideMainThread) {
-            mRule.mProvideMainThread = provideMainThread;
+            mBuilder.setProvideMainThread(provideMainThread);
             return this;
         }
 
@@ -184,10 +155,8 @@
          *
          * Has no effect on non-Ravenwood environments.
          */
-        public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
-                /* @Nullable */ Object value) {
-            mRule.mSystemProperties.setValue(key, value);
-            mRule.mSystemProperties.setAccessReadOnly(key);
+        public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
+            mBuilder.setSystemPropertyImmutable(key, value);
             return this;
         }
 
@@ -201,10 +170,8 @@
          *
          * Has no effect on non-Ravenwood environments.
          */
-        public Builder setSystemPropertyMutable(/* @NonNull */ String key,
-                /* @Nullable */ Object value) {
-            mRule.mSystemProperties.setValue(key, value);
-            mRule.mSystemProperties.setAccessReadWrite(key);
+        public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
+            mBuilder.setSystemPropertyMutable(key, value);
             return this;
         }
 
@@ -217,16 +184,13 @@
          * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
          * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
          */
-        public Builder setServicesRequired(Class<?>... services) {
-            mRule.mServicesRequired.clear();
-            for (Class<?> service : services) {
-                mRule.mServicesRequired.add(service);
-            }
+        public Builder setServicesRequired(@NonNull Class<?>... services) {
+            mBuilder.setServicesRequired(services);
             return this;
         }
 
         public RavenwoodRule build() {
-            return mRule;
+            return new RavenwoodRule(mBuilder.build());
         }
     }
 
@@ -246,41 +210,44 @@
     }
 
     /**
-     * Return a {@code Context} available for usage during the currently running test case.
-     *
-     * Each test should obtain needed information or references via this method;
-     * references must not be stored beyond the scope of a test case.
+     * @deprecated Use
+     * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()}
+     * instead.
      */
+    @Deprecated
     public Context getContext() {
-        return Objects.requireNonNull(mContext,
+        return Objects.requireNonNull(mConfiguration.mContext,
                 "Context is only available during @Test execution");
     }
 
     /**
-     * Return a {@code Instrumentation} available for usage during the currently running test case.
-     *
-     * Each test should obtain needed information or references via this method;
-     * references must not be stored beyond the scope of a test case.
+     * @deprecated Use
+     * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()}
+     * instead.
      */
+    @Deprecated
     public Instrumentation getInstrumentation() {
-        return Objects.requireNonNull(mInstrumentation,
+        return Objects.requireNonNull(mConfiguration.mInstrumentation,
                 "Instrumentation is only available during @Test execution");
     }
 
-
     @Override
     public Statement apply(Statement base, Description description) {
-        // TODO: Here, we're calling init() / reset() once for each rule.
-        // That means if a test class has multiple rules -- even if they refer to the same
-        // rule instance -- we're calling them multiple times. We need to fix it.
+        if (!RavenwoodConfig.isOnRavenwood()) {
+            return base;
+        }
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                RavenwoodRuleImpl.init(RavenwoodRule.this);
+                RavenwoodAwareTestRunnerHook.onRavenwoodRuleEnter(
+                        RavenwoodAwareTestRunner.getCurrentRunner(), description,
+                        RavenwoodRule.this);
                 try {
                     base.evaluate();
                 } finally {
-                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                    RavenwoodAwareTestRunnerHook.onRavenwoodRuleExit(
+                            RavenwoodAwareTestRunner.getCurrentRunner(), description,
+                            RavenwoodRule.this);
                 }
             }
         };
@@ -339,4 +306,8 @@
     public static RavenwoodPrivate private$ravenwood() {
         return sRavenwoodPrivate;
     }
+
+    RavenwoodConfig getConfiguration() {
+        return mConfiguration;
+    }
 }
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 1e4889c..0178b93 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -19,7 +19,6 @@
 import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
 
 import org.junit.runner.Description;
-import org.junit.runner.Runner;
 import org.junit.runners.model.TestClass;
 
 /**
@@ -38,7 +37,7 @@
     /**
      * Called when a runner starts, before the inner runner gets a chance to run.
      */
-    public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+    public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
     }
 
     /**
@@ -48,15 +47,38 @@
     }
 
     /**
+     * Called before the inner runner starts.
+     */
+    public static void onBeforeInnerRunnerStart(
+            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
+    }
+
+    /**
+     * Called after the inner runner finished.
+     */
+    public static void onAfterInnerRunnerFinished(
+            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
+    }
+
+    /**
      * Called before a test / class.
      *
      * Return false if it should be skipped.
      */
     public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order) {
+            Scope scope, Order order) throws Throwable {
         return true;
     }
 
+    public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
+            Description description, RavenwoodRule rule) throws Throwable {
+    }
+
+    public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
+            Description description, RavenwoodRule rule) throws Throwable {
+    }
+
+
     /**
      * Called after a test / class.
      *
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
deleted file mode 100644
index a470626..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.platform.test.ravenwood;
-
-import org.junit.runner.Description;
-
-public class RavenwoodRuleImpl {
-    public static void init(RavenwoodRule rule) {
-        // No-op when running on a real device
-    }
-
-    public static void reset(RavenwoodRule rule) {
-        // No-op when running on a real device
-    }
-
-    public static void logTestRunner(String label, Description description) {
-        // No-op when running on a real device
-    }
-
-    public static long realCurrentTimeMillis() {
-        return System.currentTimeMillis();
-    }
-}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 875ce71..96746c6 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -26,6 +26,7 @@
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
@@ -279,6 +280,18 @@
                 (isStatic ? "static " : "")));
     }
 
+    public static void ensureIsPublicMember(Member member, boolean isStatic) {
+        var ok = Modifier.isPublic(member.getModifiers())
+                && (Modifier.isStatic(member.getModifiers()) == isStatic);
+        if (ok) {
+            return; // okay
+        }
+        throw new AssertionError(String.format(
+                "%s.%s expected to be public %s",
+                member.getDeclaringClass().getName(), member.getName(),
+                (isStatic ? "static" : "")));
+    }
+
     @NonNull
     public static String getStackTraceString(@Nullable Throwable th) {
         StringWriter stringWriter = new StringWriter();
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Parcel_host.java b/ravenwood/runtime-helper-src/framework/android/os/Parcel_host.java
deleted file mode 100644
index 720f1d2..0000000
--- a/ravenwood/runtime-helper-src/framework/android/os/Parcel_host.java
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.os;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Tentative, partial implementation of the Parcel native methods, using Java's
- * {@code byte[]}.
- * (We don't use a {@link ByteBuffer} because there's enough semantics differences between Parcel
- * and {@link ByteBuffer}, and it didn't work out.
- * e.g. Parcel seems to allow moving the data position to be beyond its size? Which
- * {@link ByteBuffer} wouldn't allow...)
- */
-public class Parcel_host {
-    private static final String TAG = "Parcel";
-
-    private Parcel_host() {
-    }
-
-    private static final AtomicLong sNextId = new AtomicLong(1);
-
-    private static final Map<Long, Parcel_host> sInstances = new ConcurrentHashMap<>();
-
-    private boolean mDeleted = false;
-
-    private byte[] mBuffer;
-    private int mSize;
-    private int mPos;
-
-    private boolean mSensitive;
-    private boolean mAllowFds;
-
-    // TODO Use the actual value from Parcel.java.
-    private static final int OK = 0;
-
-    private final Map<Integer, FileDescriptor> mFdMap = new ConcurrentHashMap<>();
-
-    private static final int FD_PLACEHOLDER = 0xDEADBEEF;
-    private static final int FD_PAYLOAD_SIZE = 8;
-
-    private void validate() {
-        if (mDeleted) {
-            // TODO: Put more info
-            throw new RuntimeException("Parcel already destroyed");
-        }
-    }
-
-    private static Parcel_host getInstance(long id) {
-        Parcel_host p = sInstances.get(id);
-        if (p == null) {
-            // TODO: Put more info
-            throw new RuntimeException("Parcel doesn't exist with id=" + id);
-        }
-        p.validate();
-        return p;
-    }
-
-    /** Native method substitution */
-    public static long nativeCreate() {
-        final long id = sNextId.getAndIncrement();
-        final Parcel_host p = new Parcel_host();
-        sInstances.put(id, p);
-        p.init();
-        return id;
-    }
-
-    private void init() {
-        mBuffer = new byte[0];
-        mSize = 0;
-        mPos = 0;
-        mSensitive = false;
-        mAllowFds = true;
-        mFdMap.clear();
-    }
-
-    private void updateSize() {
-        if (mSize < mPos) {
-            mSize = mPos;
-        }
-    }
-
-    /** Native method substitution */
-    public static void nativeDestroy(long nativePtr) {
-        getInstance(nativePtr).mDeleted = true;
-        sInstances.remove(nativePtr);
-    }
-
-    /** Native method substitution */
-    public static void nativeFreeBuffer(long nativePtr) {
-        getInstance(nativePtr).freeBuffer();
-    }
-
-    /** Native method substitution */
-    private void freeBuffer() {
-        init();
-    }
-
-    private int getCapacity() {
-        return mBuffer.length;
-    }
-
-    private void ensureMoreCapacity(int size) {
-        ensureCapacity(mPos + size);
-    }
-
-    private void ensureCapacity(int targetSize) {
-        if (targetSize <= getCapacity()) {
-            return;
-        }
-        var newSize = getCapacity() * 2;
-        if (newSize < targetSize) {
-            newSize = targetSize;
-        }
-        forceSetCapacity(newSize);
-    }
-
-    private void forceSetCapacity(int newSize) {
-        var newBuf = new byte[newSize];
-
-        // Copy
-        System.arraycopy(mBuffer, 0, newBuf, 0, Math.min(newSize, getCapacity()));
-
-        this.mBuffer = newBuf;
-    }
-
-    private void ensureDataAvailable(int requestSize) {
-        if (mSize - mPos < requestSize) {
-            throw new RuntimeException(String.format(
-                    "Pacel data underflow. size=%d, pos=%d, request=%d", mSize, mPos, requestSize));
-        }
-    }
-
-    /** Native method substitution */
-    public static void nativeMarkSensitive(long nativePtr) {
-        getInstance(nativePtr).mSensitive = true;
-    }
-
-    /** Native method substitution */
-    public static int nativeDataSize(long nativePtr) {
-        return getInstance(nativePtr).mSize;
-    }
-
-    /** Native method substitution */
-    public static int nativeDataAvail(long nativePtr) {
-        var p = getInstance(nativePtr);
-        return p.mSize - p.mPos;
-    }
-
-    /** Native method substitution */
-    public static int nativeDataPosition(long nativePtr) {
-        return getInstance(nativePtr).mPos;
-    }
-
-    /** Native method substitution */
-    public static int nativeDataCapacity(long nativePtr) {
-        return getInstance(nativePtr).mBuffer.length;
-    }
-
-    /** Native method substitution */
-    public static void nativeSetDataSize(long nativePtr, int size) {
-        var p = getInstance(nativePtr);
-        p.ensureCapacity(size);
-        getInstance(nativePtr).mSize = size;
-    }
-
-    /** Native method substitution */
-    public static void nativeSetDataPosition(long nativePtr, int pos) {
-        var p = getInstance(nativePtr);
-        // TODO: Should this change the size or the capacity??
-        p.mPos = pos;
-    }
-
-    /** Native method substitution */
-    public static void nativeSetDataCapacity(long nativePtr, int size) {
-        if (size < 0) {
-            throw new IllegalArgumentException("size < 0: size=" + size);
-        }
-        var p = getInstance(nativePtr);
-        if (p.getCapacity() < size) {
-            p.forceSetCapacity(size);
-        }
-    }
-
-    /** Native method substitution */
-    public static boolean nativePushAllowFds(long nativePtr, boolean allowFds) {
-        var p = getInstance(nativePtr);
-        var prev = p.mAllowFds;
-        p.mAllowFds = allowFds;
-        return prev;
-    }
-
-    /** Native method substitution */
-    public static void nativeRestoreAllowFds(long nativePtr, boolean lastValue) {
-        getInstance(nativePtr).mAllowFds = lastValue;
-    }
-
-    /** Native method substitution */
-    public static void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len) {
-        nativeWriteBlob(nativePtr, b, offset, len);
-    }
-
-    /** Native method substitution */
-    public static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) {
-        var p = getInstance(nativePtr);
-
-        if (b == null) {
-            nativeWriteInt(nativePtr, -1);
-        } else {
-            final var alignedSize = align4(len);
-
-            nativeWriteInt(nativePtr, len);
-
-            p.ensureMoreCapacity(alignedSize);
-
-            System.arraycopy(b, offset, p.mBuffer,  p.mPos, len);
-            p.mPos += alignedSize;
-            p.updateSize();
-        }
-    }
-
-    /** Native method substitution */
-    public static int nativeWriteInt(long nativePtr, int value) {
-        var p = getInstance(nativePtr);
-        p.ensureMoreCapacity(Integer.BYTES);
-
-        p.mBuffer[p.mPos++] = (byte) ((value >> 24) & 0xff);
-        p.mBuffer[p.mPos++] = (byte) ((value >> 16) & 0xff);
-        p.mBuffer[p.mPos++] = (byte) ((value >>  8) & 0xff);
-        p.mBuffer[p.mPos++] = (byte) ((value >>  0) & 0xff);
-
-        p.updateSize();
-
-        return OK;
-    }
-
-    /** Native method substitution */
-    public static int nativeWriteLong(long nativePtr, long value) {
-        nativeWriteInt(nativePtr, (int) (value >>> 32));
-        nativeWriteInt(nativePtr, (int) (value));
-        return OK;
-    }
-
-    /** Native method substitution */
-    public static int nativeWriteFloat(long nativePtr, float val) {
-        return nativeWriteInt(nativePtr, Float.floatToIntBits(val));
-    }
-
-    /** Native method substitution */
-    public static int nativeWriteDouble(long nativePtr, double val) {
-        return nativeWriteLong(nativePtr, Double.doubleToLongBits(val));
-    }
-
-    private static int align4(int val) {
-        return ((val + 3) / 4) * 4;
-    }
-
-    /** Native method substitution */
-    public static void nativeWriteString8(long nativePtr, String val) {
-        if (val == null) {
-            nativeWriteBlob(nativePtr, null, 0, 0);
-        } else {
-            var bytes = val.getBytes(StandardCharsets.UTF_8);
-            nativeWriteBlob(nativePtr, bytes, 0, bytes.length);
-        }
-    }
-
-    /** Native method substitution */
-    public static void nativeWriteString16(long nativePtr, String val) {
-        // Just reuse String8
-        nativeWriteString8(nativePtr, val);
-    }
-
-    /** Native method substitution */
-    public static byte[] nativeCreateByteArray(long nativePtr) {
-        return nativeReadBlob(nativePtr);
-    }
-
-    /** Native method substitution */
-    public static boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen) {
-        if (dest == null) {
-            return false;
-        }
-        var data = nativeReadBlob(nativePtr);
-        if (data == null) {
-            System.err.println("Percel has NULL, which is unexpected."); // TODO: Is this correct?
-            return false;
-        }
-        // TODO: Make sure the check logic is correct.
-        if (data.length != destLen) {
-            System.err.println("Byte array size mismatch: expected="
-                    + data.length + " given=" + destLen);
-            return false;
-        }
-        System.arraycopy(data, 0, dest, 0, data.length);
-        return true;
-    }
-
-    /** Native method substitution */
-    public static byte[] nativeReadBlob(long nativePtr) {
-        var p = getInstance(nativePtr);
-        if (p.mSize - p.mPos < 4) {
-            // Match native impl that returns "null" when not enough data
-            return null;
-        }
-        final var size = nativeReadInt(nativePtr);
-        if (size == -1) {
-            return null;
-        }
-        try {
-            p.ensureDataAvailable(align4(size));
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            return null;
-        }
-
-        var bytes = new byte[size];
-        System.arraycopy(p.mBuffer, p.mPos, bytes, 0, size);
-
-        p.mPos += align4(size);
-
-        return bytes;
-    }
-
-    /** Native method substitution */
-    public static int nativeReadInt(long nativePtr) {
-        var p = getInstance(nativePtr);
-
-        if (p.mSize - p.mPos < 4) {
-            // Match native impl that returns "0" when not enough data
-            return 0;
-        }
-
-        var ret = (((p.mBuffer[p.mPos++] & 0xff) << 24)
-                | ((p.mBuffer[p.mPos++] & 0xff) << 16)
-                | ((p.mBuffer[p.mPos++] & 0xff) <<  8)
-                | ((p.mBuffer[p.mPos++] & 0xff) <<  0));
-
-        return ret;
-    }
-
-    /** Native method substitution */
-    public static long nativeReadLong(long nativePtr) {
-        return (((long) nativeReadInt(nativePtr)) << 32)
-                | (((long) nativeReadInt(nativePtr)) & 0xffff_ffffL);
-    }
-
-    /** Native method substitution */
-    public static float nativeReadFloat(long nativePtr) {
-        return Float.intBitsToFloat(nativeReadInt(nativePtr));
-    }
-
-    /** Native method substitution */
-    public static double nativeReadDouble(long nativePtr) {
-        return Double.longBitsToDouble(nativeReadLong(nativePtr));
-    }
-
-    /** Native method substitution */
-    public static String nativeReadString8(long nativePtr) {
-        final var bytes = nativeReadBlob(nativePtr);
-        if (bytes == null) {
-            return null;
-        }
-        return new String(bytes, StandardCharsets.UTF_8);
-    }
-    public static String nativeReadString16(long nativePtr) {
-        return nativeReadString8(nativePtr);
-    }
-
-    /** Native method substitution */
-    public static byte[] nativeMarshall(long nativePtr) {
-        var p = getInstance(nativePtr);
-        return Arrays.copyOf(p.mBuffer, p.mSize);
-    }
-
-    /** Native method substitution */
-    public static void nativeUnmarshall(
-            long nativePtr, byte[] data, int offset, int length) {
-        var p = getInstance(nativePtr);
-        p.ensureMoreCapacity(length);
-        System.arraycopy(data, offset, p.mBuffer, p.mPos, length);
-        p.mPos += length;
-        p.updateSize();
-    }
-
-    /** Native method substitution */
-    public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
-        var a = getInstance(thisNativePtr);
-        var b = getInstance(otherNativePtr);
-        if ((a.mSize == b.mSize) && Arrays.equals(a.mBuffer, b.mBuffer)) {
-            return 0;
-        } else {
-            return -1;
-        }
-    }
-
-    /** Native method substitution */
-    public static boolean nativeCompareDataInRange(
-            long ptrA, int offsetA, long ptrB, int offsetB, int length) {
-        var a = getInstance(ptrA);
-        var b = getInstance(ptrB);
-        if (offsetA < 0 || offsetA + length > a.mSize) {
-            throw new IllegalArgumentException();
-        }
-        if (offsetB < 0 || offsetB + length > b.mSize) {
-            throw new IllegalArgumentException();
-        }
-        return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length),
-                Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length));
-    }
-
-    /** Native method substitution */
-    public static void nativeAppendFrom(
-            long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
-        var dst = getInstance(thisNativePtr);
-        var src = getInstance(otherNativePtr);
-
-        dst.ensureMoreCapacity(length);
-
-        System.arraycopy(src.mBuffer, srcOffset, dst.mBuffer, dst.mPos, length);
-        dst.mPos += length; // TODO: 4 byte align?
-        dst.updateSize();
-
-        // TODO: Update the other's position?
-    }
-
-    /** Native method substitution */
-    public static boolean nativeHasBinders(long nativePtr) {
-        // Assume false for now, because we don't support adding binders.
-        return false;
-    }
-
-    /** Native method substitution */
-    public static boolean nativeHasBindersInRange(
-            long nativePtr, int offset, int length) {
-        // Assume false for now, because we don't support writing FDs yet.
-        return false;
-    }
-
-    /** Native method substitution */
-    public static void nativeWriteFileDescriptor(long nativePtr, java.io.FileDescriptor val) {
-        var p = getInstance(nativePtr);
-
-        if (!p.mAllowFds) {
-            // Simulate the FDS_NOT_ALLOWED case in frameworks/base/core/jni/android_util_Binder.cpp
-            throw new RuntimeException("Not allowed to write file descriptors here");
-        }
-
-        FileDescriptor dup = null;
-        try {
-            dup = Os.dup(val);
-        } catch (ErrnoException e) {
-            throw new RuntimeException(e);
-        }
-        p.mFdMap.put(p.mPos, dup);
-
-        // Parcel.cpp writes two int32s for a FD.
-        // Make sure FD_PAYLOAD_SIZE is in sync with this code.
-        nativeWriteInt(nativePtr, FD_PLACEHOLDER);
-        nativeWriteInt(nativePtr, FD_PLACEHOLDER);
-    }
-
-    /** Native method substitution */
-    public static java.io.FileDescriptor nativeReadFileDescriptor(long nativePtr) {
-        var p = getInstance(nativePtr);
-
-        var pos = p.mPos;
-        var fd = p.mFdMap.get(pos);
-
-        if (fd == null) {
-            Log.w(TAG, "nativeReadFileDescriptor: Not a FD at pos #" + pos);
-            return null;
-        }
-        nativeReadInt(nativePtr);
-        nativeReadInt(nativePtr);
-        return fd;
-    }
-
-    /** Native method substitution */
-    public static boolean nativeHasFileDescriptors(long nativePtr) {
-        var p = getInstance(nativePtr);
-        return p.mFdMap.size() > 0;
-    }
-
-    /** Native method substitution */
-    public static boolean nativeHasFileDescriptorsInRange(long nativePtr, int offset, int length) {
-        var p = getInstance(nativePtr);
-
-        // Original code: hasFileDescriptorsInRange() in frameworks/native/libs/binder/Parcel.cpp
-        if (offset < 0 || length < 0) {
-            throw new IllegalArgumentException("Negative value not allowed: offset=" + offset
-                    + " length=" + length);
-        }
-        long limit = (long) offset + (long) length;
-        if (limit > p.mSize) {
-            throw new IllegalArgumentException("Out of range: offset=" + offset
-                    + " length=" + length + " dataSize=" + p.mSize);
-        }
-
-        for (var pos : p.mFdMap.keySet()) {
-            if (offset <= pos && (pos + FD_PAYLOAD_SIZE - 1) < (offset + length)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 01e19a7..790bb1c 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -166,6 +166,7 @@
     private static final Class<?>[] sLibandroidClasses = {
             android.util.Log.class,
             android.os.Parcel.class,
+            android.os.Binder.class,
             android.content.res.ApkAssets.class,
             android.content.res.AssetManager.class,
             android.content.res.StringBlock.class,
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
new file mode 100644
index 0000000..d94475c
--- /dev/null
+++ b/ravenwood/tests/coretest/Android.bp
@@ -0,0 +1,25 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+    name: "RavenwoodCoreTest",
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "junit-params",
+        "platform-parametric-runner-lib",
+        "truth",
+    ],
+    srcs: [
+        "test/**/*.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
new file mode 100644
index 0000000..6d8fb98
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runnercallbacktests;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+
+/**
+ * Tests to make sure {@link RavenwoodAwareTestRunner} produces expected callbacks in various
+ * error situations in places such as @BeforeClass / @AfterClass / Constructors, which are
+ * out of test method bodies.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase {
+
+    /**
+     * Throws an exception in @AfterClass. This should produce a critical error.
+     */
+    @RunWith(BlockJUnit4ClassRunner.class)
+    // CHECKSTYLE:OFF Generated code
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest
+    testStarted: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest)
+    testFinished: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest)
+    testStarted: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest)
+    testFinished: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$AfterClassFailureTest
+    criticalError: Failures detected in @AfterClass, which would be swallowed by tradefed: FAILURE
+    testSuiteFinished: classes
+    testRunFinished: 2,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class AfterClassFailureTest {
+        public AfterClassFailureTest() {
+        }
+
+        @AfterClass
+        public static void afterClass() {
+            throw new RuntimeException("FAILURE");
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+
+    }
+
+    /**
+     * Assumption failure in @BeforeClass.
+     */
+    @RunWith(ParameterizedAndroidJunit4.class)
+    // Because the test uses ParameterizedAndroidJunit4 with two parameters,
+    // the whole class is executed twice.
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest
+    testSuiteStarted: [0]
+    testStarted: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testStarted: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testSuiteFinished: [0]
+    testSuiteStarted: [1]
+    testStarted: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testStarted: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassAssumptionFailureTest)
+    testSuiteFinished: [1]
+    testSuiteFinished: classes
+    testRunFinished: 4,0,4,0
+    """)
+    // CHECKSTYLE:ON
+    public static class BeforeClassAssumptionFailureTest {
+        public BeforeClassAssumptionFailureTest(String param) {
+        }
+
+        @BeforeClass
+        public static void beforeClass() {
+            Assume.assumeTrue(false);
+        }
+
+        @Parameters
+        public static List<String> getParams() {
+            var params =  new ArrayList<String>();
+            params.add("foo");
+            params.add("bar");
+            return params;
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
+
+    /**
+     * General exception in @BeforeClass.
+     */
+    @RunWith(ParameterizedAndroidJunit4.class)
+    // Because the test uses ParameterizedAndroidJunit4 with two parameters,
+    // the whole class is executed twice.
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest
+    testSuiteStarted: [0]
+    testStarted: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testFailure: FAILURE
+    testFinished: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testStarted: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testFailure: FAILURE
+    testFinished: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testSuiteFinished: [0]
+    testSuiteStarted: [1]
+    testStarted: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testFailure: FAILURE
+    testFinished: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testStarted: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testFailure: FAILURE
+    testFinished: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BeforeClassExceptionTest)
+    testSuiteFinished: [1]
+    testSuiteFinished: classes
+    testRunFinished: 4,4,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class BeforeClassExceptionTest {
+        public BeforeClassExceptionTest(String param) {
+        }
+
+        @BeforeClass
+        public static void beforeClass() {
+            throw new RuntimeException("FAILURE");
+        }
+
+        @Parameters
+        public static List<String> getParams() {
+            var params =  new ArrayList<String>();
+            params.add("foo");
+            params.add("bar");
+            return params;
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
+
+    /**
+     * Assumption failure from a @ClassRule.
+     */
+    @RunWith(ParameterizedAndroidJunit4.class)
+    // Because the test uses ParameterizedAndroidJunit4 with two parameters,
+    // the whole class is executed twice.
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest
+    testSuiteStarted: [0]
+    testStarted: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testStarted: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testSuiteFinished: [0]
+    testSuiteStarted: [1]
+    testStarted: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testStarted: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleAssumptionFailureTest)
+    testSuiteFinished: [1]
+    testSuiteFinished: classes
+    testRunFinished: 4,0,4,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ClassRuleAssumptionFailureTest {
+        @ClassRule
+        public static final TestRule sClassRule = new TestRule() {
+            @Override
+            public Statement apply(Statement base, Description description) {
+                assumeTrue(false);
+                return null; // unreachable
+            }
+        };
+
+        public ClassRuleAssumptionFailureTest(String param) {
+        }
+
+        @Parameters
+        public static List<String> getParams() {
+            var params = new ArrayList<String>();
+            params.add("foo");
+            params.add("bar");
+            return params;
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
+
+    /**
+     * General exception from a @ClassRule.
+     */
+    @RunWith(ParameterizedAndroidJunit4.class)
+    // Because the test uses ParameterizedAndroidJunit4 with two parameters,
+    // the whole class is executed twice.
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest
+    testSuiteStarted: [0]
+    testStarted: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testStarted: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[0](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testSuiteFinished: [0]
+    testSuiteStarted: [1]
+    testStarted: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test1[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testStarted: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testAssumptionFailure: got: <false>, expected: is <true>
+    testFinished: test2[1](com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassRuleExceptionTest)
+    testSuiteFinished: [1]
+    testSuiteFinished: classes
+    testRunFinished: 4,0,4,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ClassRuleExceptionTest {
+        @ClassRule
+        public static final TestRule sClassRule = new TestRule() {
+            @Override
+            public Statement apply(Statement base, Description description) {
+                assumeTrue(false);
+                return null; // unreachable
+            }
+        };
+
+        public ClassRuleExceptionTest(String param) {
+        }
+
+        @Parameters
+        public static List<String> getParams() {
+            var params = new ArrayList<String>();
+            params.add("foo");
+            params.add("bar");
+            return params;
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
+
+    /**
+     * General exception from a @ClassRule.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
+    testFailure: Exception detected in constructor
+    testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ExceptionFromInnerRunnerConstructorTest {
+        public ExceptionFromInnerRunnerConstructorTest(String arg1, String arg2) {
+        }
+
+        @Test
+        public void test1() {
+        }
+
+        @Test
+        public void test2() {
+        }
+    }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
new file mode 100644
index 0000000..6ee443f
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Test for @Config field extraction and validation.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
+    public abstract static class ConfigInBaseClass {
+        static String PACKAGE_NAME = "com.ConfigInBaseClass";
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
+                .setPackageName(PACKAGE_NAME).build();
+    }
+
+    /**
+     * Make sure a config in the base class is detected.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
+    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
+    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ConfigInBaseClassTest extends ConfigInBaseClass {
+        @Test
+        public void test() {
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
+                    .isEqualTo(PACKAGE_NAME);
+        }
+    }
+
+    /**
+     * Make sure a config in the base class is detected.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
+    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
+    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ConfigOverridingTest extends ConfigInBaseClass {
+        static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
+                .setPackageName(PACKAGE_NAME_OVERRIDE).build();
+
+        @Test
+        public void test() {
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
+                    .isEqualTo(PACKAGE_NAME_OVERRIDE);
+        }
+    }
+
+    /**
+     * Test to make sure that if a test has a config error, the failure would be reported from
+     * each test method.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testMethod1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
+    testFinished: testMethod1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testStarted: testMethod2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
+    testFinished: testMethod2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testStarted: testMethod3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
+    testFinished: testMethod3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testSuiteFinished: classes
+    testRunFinished: 3,3,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ErrorMustBeReportedFromEachTest {
+        @RavenwoodConfig.Config
+        private static RavenwoodConfig sConfig = // Invalid because it's private.
+                new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void testMethod1() {
+        }
+
+        @Test
+        public void testMethod2() {
+        }
+
+        @Test
+        public void testMethod3() {
+        }
+    }
+
+    /**
+     * Invalid because there are two @Config's.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
+    testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfiguration.Config
+    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class DuplicateConfigTest {
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig1 =
+                new RavenwoodConfig.Builder().build();
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig2 =
+                new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+    }
+
+    /**
+     * @Config's must be static.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
+    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
+    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class NonStaticConfigTest {
+
+        @RavenwoodConfig.Config
+        public RavenwoodConfig sConfig =
+                new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+    }
+
+    /**
+     * @Config's must be public.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
+    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
+    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class NonPublicConfigTest {
+
+        @RavenwoodConfig.Config
+        RavenwoodConfig sConfig =
+                new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+    }
+
+    /**
+     * @Config's must be of type RavenwoodConfiguration.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
+    testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfiguration.Config but type is not RavenwoodConfiguration
+    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class WrongTypeConfigTest {
+
+        @RavenwoodConfig.Config
+        public Object sConfig =
+                new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+
+    }
+
+    /**
+     * Config can't be used with a (instance) Rule.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
+    testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class WithInstanceRuleTest {
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig =
+                new RavenwoodConfig.Builder().build();
+
+        @Rule
+        public RavenwoodRule mRule = new RavenwoodRule.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+    }
+
+    /**
+     * Config can't be used with a (static) Rule.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
+    testFailure: Exception detected in constructor
+    testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class WithStaticRuleTest {
+
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig =
+                new RavenwoodConfig.Builder().build();
+
+        @Rule
+        public static RavenwoodRule sRule = new RavenwoodRule.Builder().build();
+
+        @Test
+        public void testConfig() {
+        }
+    }
+
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
+    testStarted: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
+    testFailure: Multiple nesting RavenwoodRule's are detected in the same class, which is not supported.
+    testFinished: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class DuplicateRulesTest {
+
+        @Rule
+        public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
+
+        @Rule
+        public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
+
+        @Test
+        public void testMultipleRulesNotAllowed() {
+        }
+    }
+
+    public static class RuleInBaseClass {
+        static String PACKAGE_NAME = "com.RuleInBaseClass";
+        @Rule
+        public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setPackageName(PACKAGE_NAME).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
+                    .isEqualTo(PACKAGE_NAME);
+        }
+    }
+
+    /**
+     * Make sure that having a config and a rule in a base class should fail.
+     * RavenwoodRule.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
+    testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ConfigWithRuleInBaseClassTest extends RuleInBaseClass {
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void test() {
+        }
+    }
+
+    /**
+     * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
+     */
+    public abstract static class RuleWithDifferentTypeInBaseClass {
+        static String PACKAGE_NAME = "com.RuleWithDifferentTypeInBaseClass";
+        @Rule
+        public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setPackageName(PACKAGE_NAME).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
+                    .isEqualTo(PACKAGE_NAME);
+        }
+    }
+
+    /**
+     * Make sure that having a config and a rule in a base class should fail, even if the field type is not
+     * RavenwoodRule.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
+    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
+    testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
+    testSuiteFinished: classes
+    testRunFinished: 1,1,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class ConfigWithRuleWithDifferentTypeInBaseClassTest extends RuleWithDifferentTypeInBaseClass {
+        @RavenwoodConfig.Config
+        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
+
+        @Test
+        public void test() {
+        }
+    }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
new file mode 100644
index 0000000..9a6934b
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
+import android.util.Log;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+
+
+/**
+ * Base class for tests to make sure {@link RavenwoodAwareTestRunner} produces expected callbacks
+ * in various situations. (most of them are error situations.)
+ *
+ * Subclasses must contain test classes as static inner classes with an {@link Expected} annotation.
+ * This class finds them using reflections and run them one by one directly using {@link JUnitCore},
+ * and check the callbacks.
+ *
+ * Subclasses do no need to have any test methods.
+ *
+ * The {@link Expected} annotation must contain the expected result as a string.
+ *
+ * This test abuses the fact that atest + tradefed + junit won't run nested classes automatically.
+ * (So atest won't show any results directly from the nested classes.)
+ *
+ * The actual test method is {@link #doTest}, which is executed for each target test class, using
+ * junit-params.
+ */
+@RunWith(JUnitParamsRunner.class)
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public abstract class RavenwoodRunnerTestBase {
+    private static final String TAG = "RavenwoodRunnerTestBase";
+
+    /**
+     * Annotation to specify the expected result for a class.
+     */
+    @Target({ElementType.TYPE})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Expected {
+        String value();
+    }
+
+    /**
+     * Take a multiline string, strip all of them, remove empty lines, and return it.
+     */
+    private static String stripMultiLines(String resultString) {
+        var list = new ArrayList<String>();
+        for (var line : resultString.split("\n")) {
+            var s = line.strip();
+            if (s.length() > 0) {
+                list.add(s);
+            }
+        }
+        return String.join("\n", list);
+    }
+
+    /**
+     * Extract the expected result from @Expected.
+     */
+    private String getExpectedResult(Class<?> testClazz) {
+        var expect = testClazz.getAnnotation(Expected.class);
+        return stripMultiLines(expect.value());
+    }
+
+    /**
+     * List all the nested classrs with an {@link Expected} annotation in a given class.
+     */
+    public Class<?>[] getTestClasses() {
+        var thisClass = this.getClass();
+        var ret = Arrays.stream(thisClass.getNestMembers())
+                .filter((c) -> c.getAnnotation(Expected.class) != null)
+                .toArray(Class[]::new);
+
+        assertThat(ret.length).isGreaterThan(0);
+
+        return ret;
+    }
+
+    /**
+     * This is the actual test method. We use junit-params to run this method for each target
+     * test class, which are returned by {@link #getTestClasses}.
+     *
+     * It runs each test class, and compare the result collected with
+     * {@link ResultCollectingListener} to expected results (as strings).
+     */
+    @Test
+    @Parameters(method = "getTestClasses")
+    public void doTest(Class<?> testClazz) {
+        doTest(testClazz, getExpectedResult(testClazz));
+    }
+
+    /**
+     * Run a given test class, and compare the result collected with
+     * {@link ResultCollectingListener} to expected results (as a string).
+     */
+    private void doTest(Class<?> testClazz, String expectedResult) {
+        Log.i(TAG, "Running test for " + testClazz);
+        var junitCore = new JUnitCore();
+
+        // Create a listener.
+        var listener = new ResultCollectingListener();
+        junitCore.addListener(listener);
+
+        // Set a listener to critical errors. This will also prevent
+        // {@link RavenwoodAwareTestRunner} from calling System.exit() when there's
+        // a critical error.
+        RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(
+                listener.sCriticalErrorListener);
+
+        try {
+            // Run the test class.
+            junitCore.run(testClazz);
+        } finally {
+            // Clear the critical error listener.
+            RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(null);
+        }
+
+        // Check the result.
+        assertWithMessage("Failure in test class: " + testClazz.getCanonicalName() + "]")
+                .that(listener.getResult())
+                .isEqualTo(expectedResult);
+    }
+
+    /**
+     * A JUnit RunListener that collects all the callbacks as a single string.
+     */
+    private static class ResultCollectingListener extends RunListener {
+        private final ArrayList<String> mResult = new ArrayList<>();
+
+        public final BiConsumer<String, Throwable> sCriticalErrorListener = (message, th) -> {
+            mResult.add("criticalError: " + message + ": " + th.getMessage());
+        };
+
+        @Override
+        public void testRunStarted(Description description) throws Exception {
+            mResult.add("testRunStarted: " + description);
+        }
+
+        @Override
+        public void testRunFinished(Result result) throws Exception {
+            mResult.add("testRunFinished: "
+                    + result.getRunCount() + ","
+                    + result.getFailureCount() + ","
+                    + result.getAssumptionFailureCount() + ","
+                    + result.getIgnoreCount());
+        }
+
+        @Override
+        public void testSuiteStarted(Description description) throws Exception {
+            mResult.add("testSuiteStarted: " + description);
+        }
+
+        @Override
+        public void testSuiteFinished(Description description) throws Exception {
+            mResult.add("testSuiteFinished: " + description);
+        }
+
+        @Override
+        public void testStarted(Description description) throws Exception {
+            mResult.add("testStarted: " + description);
+        }
+
+        @Override
+        public void testFinished(Description description) throws Exception {
+            mResult.add("testFinished: " + description);
+        }
+
+        @Override
+        public void testFailure(Failure failure) throws Exception {
+            mResult.add("testFailure: " + failure.getException().getMessage());
+        }
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            mResult.add("testAssumptionFailure: " + failure.getException().getMessage());
+        }
+
+        @Override
+        public void testIgnored(Description description) throws Exception {
+            mResult.add("testIgnored: " + description);
+        }
+
+        public String getResult() {
+            return String.join("\n", mResult);
+        }
+    }
+}
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 3d7ad0b..b97ff62 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -10,6 +10,7 @@
 filegroup {
     name: "services.accessibility-sources",
     srcs: ["java/**/*.java"],
+    exclude_srcs: ["java/**/a11ychecker/*.java"],
     path: "java",
     visibility: ["//frameworks/base/services"],
 }
@@ -26,16 +27,13 @@
     },
     srcs: [
         ":services.accessibility-sources",
-        ":statslog-accessibility-java-gen",
         "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
     ],
     libs: [
-        "aatf",
         "services.core",
         "androidx.annotation_annotation",
     ],
     static_libs: [
-        "accessibility_protos_lite",
         "com_android_server_accessibility_flags_lib",
         "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
     ],
@@ -70,12 +68,3 @@
     name: "com_android_server_accessibility_flags_lib",
     aconfig_declarations: "com_android_server_accessibility_flags",
 }
-
-genrule {
-    name: "statslog-accessibility-java-gen",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module accessibility" +
-        " --javaPackage com.android.server.accessibility.a11ychecker" +
-        " --javaClass AccessibilityCheckerStatsLog --minApiLevel 34",
-    out: ["java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsLog.java"],
-}
diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING
index 3f85a90..0bc25e2 100644
--- a/services/accessibility/TEST_MAPPING
+++ b/services/accessibility/TEST_MAPPING
@@ -1,34 +1,19 @@
 {
   "presubmit": [
     {
-      "name": "CtsAccessibilityServiceTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAccessibilityServiceTestCases"
     },
     {
-      "name": "CtsAccessibilityTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsAccessibilityTestCases"
     },
     {
-      "name": "CtsUiAutomationTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsUiAutomationTestCases"
     },
     {
-      "name": "FrameworksServicesTests_accessibility_Presubmit"
+      "name": "FrameworksServicesTests_accessibility"
     },
     {
-      "name": "FrameworksCoreTests_accessibility_NO_FLAKES"
+      "name": "FrameworksCoreTests_accessibility"
     }
   ],
   "postsubmit": [
@@ -45,12 +30,7 @@
       "name": "CtsUiAutomationTestCases"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.accessibility"
-        }
-      ]
+      "name": "FrameworksServicesTests_accessibility"
     },
     {
       "name": "FrameworksCoreTests_accessibility"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b541345..7580b69 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -58,6 +58,7 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
+import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
 import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated;
 import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
@@ -1616,7 +1617,7 @@
 
     /**
      * Invoked remotely over AIDL by SysUi when the accessibility button within the system's
-     * navigation area has been clicked.
+     * navigation area has been clicked, or a gesture shortcut input has been performed.
      *
      * @param displayId The logical display id.
      * @param targetName The flattened {@link ComponentName} string or the class name of a system
@@ -1646,7 +1647,26 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                displayId, SOFTWARE, targetName));
+                displayId, getShortcutTypeForGenericShortcutCalls(currentUserId), targetName));
+    }
+
+    /**
+     * AIDL-exposed method to show the dialog
+     * for choosing the target for the gesture or button shortcuts.
+     * The shortcut is determined by the current navigation mode.
+     *
+     * @param displayId The id for the display to show the dialog on.
+     */
+    @Override
+    @EnforcePermission(STATUS_BAR_SERVICE)
+    public void notifyAccessibilityButtonLongClicked(int displayId) {
+        notifyAccessibilityButtonLongClicked_enforcePermission();
+        int userId;
+        synchronized (mLock) {
+            userId = mCurrentUserId;
+        }
+        showAccessibilityTargetsSelection(displayId,
+                getShortcutTypeForGenericShortcutCalls(userId), userId);
     }
 
     /**
@@ -2344,16 +2364,18 @@
         }
     }
 
-    private void showAccessibilityTargetsSelection(int displayId,
-            @UserShortcutType int shortcutType) {
+    private void showAccessibilityTargetsSelection(int displayId, int shortcutType,
+            int userId) {
         final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         final String chooserClassName = (shortcutType == HARDWARE)
                 ? AccessibilityShortcutChooserActivity.class.getName()
                 : AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra(EXTRA_TYPE_TO_CHOOSE, shortcutType);
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
-        mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
+        mMainHandler.post(() ->
+                mContext.startActivityAsUser(intent, bundle, UserHandle.of(userId)));
     }
 
     private void launchShortcutTargetActivity(int displayId, ComponentName name) {
@@ -4011,7 +4033,7 @@
      * Perform the accessibility shortcut action.
      *
      * @param shortcutType The shortcut type.
-     * @param displayId The display id of the accessibility button.
+     * @param displayId The display id the shortcut is being performed from.
      * @param targetName The flattened {@link ComponentName} string or the class name of a system
      *        class implementing a supported accessibility feature, or {@code null} if there's no
      *        specified target.
@@ -4031,7 +4053,8 @@
         if (targetName == null) {
             // In case there are many targets assigned to the given shortcut.
             if (shortcutTargets.size() > 1) {
-                showAccessibilityTargetsSelection(displayId, shortcutType);
+                showAccessibilityTargetsSelection(
+                        displayId, shortcutType, getCurrentUserState().mUserId);
                 return;
             }
             targetName = shortcutTargets.get(0);
@@ -6563,6 +6586,18 @@
                         callback));
     }
 
+    @VisibleForTesting
+    int getShortcutTypeForGenericShortcutCalls(int userId) {
+        int navigationMode = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.NAVIGATION_MODE, -1, userId);
+        if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            return (navigationMode == NAV_BAR_MODE_GESTURAL) ? GESTURE : SOFTWARE;
+        } else {
+            return SOFTWARE;
+        }
+    }
+
     void attachAccessibilityOverlayToDisplayInternal(
             int interactionId,
             int displayId,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index b18e6ba..0bf7ec00 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -57,6 +57,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.util.ShortcutUtils;
 
 import java.io.FileDescriptor;
@@ -707,11 +708,16 @@
     }
 
     /**
-     * Returns true if navibar magnification or shortcut key magnification is enabled.
+     * Returns true if a magnification shortcut of any type is enabled.
      */
     public boolean isShortcutMagnificationEnabledLocked() {
-        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
-                || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+            if (getShortcutTargetsInternalLocked(shortcutType)
+                    .contains(MAGNIFICATION_CONTROLLER_NAME)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/Android.bp b/services/accessibility/java/com/android/server/accessibility/a11ychecker/Android.bp
new file mode 100644
index 0000000..e9ed202
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// TODO(http://b/364326163): a11ychecker depends on aatf which currently can't be used in the system
+// server as it pulls in test deps. We moved a11ychecker sources from services.accessibility to an
+// isolated library while this is resolved.
+java_library_static {
+    name: "a11ychecker",
+    srcs: [
+        "*.java",
+        ":statslog-accessibility-java-gen",
+    ],
+    libs: [
+        "aatf",
+        "androidx.annotation_annotation",
+    ],
+    static_libs: [
+        "accessibility_protos_lite",
+        "com_android_server_accessibility_flags_lib",
+    ],
+}
+
+genrule {
+    name: "statslog-accessibility-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module accessibility" +
+        " --javaPackage com.android.server.accessibility.a11ychecker" +
+        " --javaClass AccessibilityCheckerStatsLog --minApiLevel 34",
+    out: ["java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsLog.java"],
+}
diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING
index c7f5eee..91e82ec 100644
--- a/services/appfunctions/TEST_MAPPING
+++ b/services/appfunctions/TEST_MAPPING
@@ -2,6 +2,9 @@
   "postsubmit": [
     {
       "name": "FrameworksAppFunctionsTests"
+    },
+    {
+      "name": "CtsAppFunctionTestCases"
     }
   ]
 }
\ No newline at end of file
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 35905ed..618a5ae 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -79,17 +79,20 @@
     // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller.
     public boolean verifyCallerCanExecuteAppFunction(
             @NonNull String callerPackageName, @NonNull String targetPackageName) {
+        if (callerPackageName.equals(targetPackageName)) {
+            return true;
+        }
+
         int pid = Binder.getCallingPid();
         int uid = Binder.getCallingUid();
-        boolean hasExecutionPermission =
+        boolean hasTrustedExecutionPermission =
                 mContext.checkPermission(
                                 Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid)
                         == PackageManager.PERMISSION_GRANTED;
-        boolean hasTrustedExecutionPermission =
+        boolean hasExecutionPermission =
                 mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid)
                         == PackageManager.PERMISSION_GRANTED;
-        boolean isSamePackage = callerPackageName.equals(targetPackageName);
-        return hasExecutionPermission || hasTrustedExecutionPermission || isSamePackage;
+        return hasExecutionPermission || hasTrustedExecutionPermission;
     }
 
     @Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index 0947238..39aa27a 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -27,6 +27,7 @@
 import android.app.appsearch.GetByDocumentIdRequest;
 import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
@@ -146,6 +147,22 @@
                         });
     }
 
+    /** Removes documents from the AppSearchSession database. */
+    public AndroidFuture<AppSearchBatchResult<String, Void>> remove(
+            @NonNull RemoveByDocumentIdRequest removeRequest) {
+        return getSessionAsync()
+                .thenCompose(
+                        session -> {
+                            AndroidFuture<AppSearchBatchResult<String, Void>>
+                                    settableBatchResultFuture = new AndroidFuture<>();
+                            session.remove(
+                                    removeRequest,
+                                    mExecutor,
+                                    new BatchResultCallbackAdapter<>(settableBatchResultFuture));
+                            return settableBatchResultFuture;
+                        });
+    }
+
     /**
      * Retrieves documents from the open AppSearchSession that match a given query string and type
      * of search provided.
@@ -200,9 +217,7 @@
         Objects.requireNonNull(namespace);
 
         GetByDocumentIdRequest request =
-                new GetByDocumentIdRequest.Builder(namespace)
-                        .addIds(documentId)
-                        .build();
+                new GetByDocumentIdRequest.Builder(namespace).addIds(documentId).build();
         return getSessionAsync()
                 .thenCompose(
                         session -> {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index be5770b..01e1008 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.WorkerThread;
+import android.app.appsearch.PropertyPath;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchSpec;
 import android.util.ArrayMap;
@@ -60,8 +61,11 @@
     @NonNull
     @VisibleForTesting
     static ArrayMap<String, ArraySet<String>> getAddedFunctionsDiffMap(
-            ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
-            ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+            @NonNull ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
+            @NonNull ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+        Objects.requireNonNull(staticPackageToFunctionMap);
+        Objects.requireNonNull(runtimePackageToFunctionMap);
+
         return getFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
     }
 
@@ -79,15 +83,21 @@
     @NonNull
     @VisibleForTesting
     static ArrayMap<String, ArraySet<String>> getRemovedFunctionsDiffMap(
-            ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
-            ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+            @NonNull ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
+            @NonNull ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+        Objects.requireNonNull(staticPackageToFunctionMap);
+        Objects.requireNonNull(runtimePackageToFunctionMap);
+
         return getFunctionsDiffMap(runtimePackageToFunctionMap, staticPackageToFunctionMap);
     }
 
     @NonNull
     private static ArrayMap<String, ArraySet<String>> getFunctionsDiffMap(
-            ArrayMap<String, ArraySet<String>> packageToFunctionMapA,
-            ArrayMap<String, ArraySet<String>> packageToFunctionMapB) {
+            @NonNull ArrayMap<String, ArraySet<String>> packageToFunctionMapA,
+            @NonNull ArrayMap<String, ArraySet<String>> packageToFunctionMapB) {
+        Objects.requireNonNull(packageToFunctionMapA);
+        Objects.requireNonNull(packageToFunctionMapB);
+
         ArrayMap<String, ArraySet<String>> diffMap = new ArrayMap<>();
         for (String packageName : packageToFunctionMapA.keySet()) {
             if (!packageToFunctionMapB.containsKey(packageName)) {
@@ -110,26 +120,32 @@
     }
 
     /**
-     * This method returns a map of package names to a set of function ids.
+     * This method returns a map of package names to a set of function ids from the AppFunction
+     * metadata.
      *
-     * @param queryExpression The query expression to use when searching for AppFunction metadata.
-     * @param metadataSearchSpec The search spec to use when searching for AppFunction metadata.
-     * @return A map of package names to a set of function ids.
-     * @throws ExecutionException If the future search results fail to execute.
-     * @throws InterruptedException If the future search results are interrupted.
+     * @param schemaType The name space of the AppFunction metadata.
+     * @return A map of package names to a set of function ids from the AppFunction metadata.
      */
     @NonNull
     @VisibleForTesting
     @WorkerThread
     ArrayMap<String, ArraySet<String>> getPackageToFunctionIdMap(
-            @NonNull String queryExpression,
-            @NonNull SearchSpec metadataSearchSpec,
+            @NonNull String schemaType,
             @NonNull String propertyFunctionId,
             @NonNull String propertyPackageName)
             throws ExecutionException, InterruptedException {
+        Objects.requireNonNull(schemaType);
+        Objects.requireNonNull(propertyFunctionId);
+        Objects.requireNonNull(propertyPackageName);
         ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
+
         FutureSearchResults futureSearchResults =
-                mFutureAppSearchSession.search(queryExpression, metadataSearchSpec).get();
+                mFutureAppSearchSession
+                        .search(
+                                "",
+                                buildMetadataSearchSpec(
+                                        schemaType, propertyFunctionId, propertyPackageName))
+                        .get();
         List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
         // TODO(b/357551503): This could be expensive if we have more functions
         while (!searchResultsList.isEmpty()) {
@@ -146,4 +162,30 @@
         }
         return packageToFunctionIds;
     }
+
+    /**
+     * This method returns a {@link SearchSpec} for searching the AppFunction metadata.
+     *
+     * @param schemaType The schema type of the AppFunction metadata.
+     * @param propertyFunctionId The property name of the function id in the AppFunction metadata.
+     * @param propertyPackageName The property name of the package name in the AppFunction metadata.
+     * @return A {@link SearchSpec} for searching the AppFunction metadata.
+     */
+    @NonNull
+    private static SearchSpec buildMetadataSearchSpec(
+            @NonNull String schemaType,
+            @NonNull String propertyFunctionId,
+            @NonNull String propertyPackageName) {
+        Objects.requireNonNull(schemaType);
+        Objects.requireNonNull(propertyFunctionId);
+        Objects.requireNonNull(propertyPackageName);
+        return new SearchSpec.Builder()
+                .addFilterSchemas(schemaType)
+                .addProjectionPaths(
+                        schemaType,
+                        List.of(
+                                new PropertyPath(propertyFunctionId),
+                                new PropertyPath(propertyPackageName)))
+                .build();
+    }
 }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1470e9a..6657c1c 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -238,13 +238,16 @@
                 final SomeArgs args = (SomeArgs) msg.obj;
                 final Context context;
                 final Intent intent;
+                final boolean forceUpdate;
                 try {
                     context = (Context) args.arg1;
                     intent = (Intent) args.arg2;
+                    forceUpdate = (Boolean) args.arg3;
                 } finally {
                     args.recycle();
                 }
-                broadcastBatteryChangedIntent(context, intent, BATTERY_CHANGED_OPTIONS);
+                broadcastBatteryChangedIntent(context, intent, BATTERY_CHANGED_OPTIONS,
+                        forceUpdate);
                 return true;
             }
             case MSG_BROADCAST_POWER_CONNECTION_CHANGED: {
@@ -798,7 +801,7 @@
             // We are doing this after sending the above broadcasts, so anything processing
             // them will get the new sequence number at that point.  (See for example how testing
             // of JobScheduler's BatteryController works.)
-            sendBatteryChangedIntentLocked();
+            sendBatteryChangedIntentLocked(force);
             if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) {
                 sendBatteryLevelChangedIntentLocked();
             }
@@ -829,7 +832,7 @@
         }
     }
 
-    private void sendBatteryChangedIntentLocked() {
+    private void sendBatteryChangedIntentLocked(boolean forceUpdate) {
         //  Pack up the values and broadcast them to everyone
         final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -869,15 +872,17 @@
             final SomeArgs args = SomeArgs.obtain();
             args.arg1 = mContext;
             args.arg2 = intent;
+            args.arg3 = forceUpdate;
             mHandler.obtainMessage(MSG_BROADCAST_BATTERY_CHANGED, args).sendToTarget();
         } else {
             mHandler.post(() -> broadcastBatteryChangedIntent(mContext,
-                    intent, BATTERY_CHANGED_OPTIONS));
+                    intent, BATTERY_CHANGED_OPTIONS, forceUpdate));
         }
     }
 
     private static void broadcastBatteryChangedIntent(Context context, Intent intent,
-            Bundle options) {
+            Bundle options, boolean forceUpdate) {
+        traceBatteryChangedBroadcastEvent(intent, forceUpdate);
         // TODO (293959093): It is important that SystemUI receives this broadcast as soon as
         // possible. Ideally, it should be using binder callbacks but until then, dispatch this
         // as a foreground broadcast to SystemUI.
@@ -895,6 +900,53 @@
                 AppOpsManager.OP_NONE, options, UserHandle.USER_ALL);
     }
 
+    private static void traceBatteryChangedBroadcastEvent(Intent intent, boolean forceUpdate) {
+        if (!com.android.server.flags.Flags.traceBatteryChangedBroadcastEvent()) {
+            return;
+        }
+        if (!Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) return;
+
+        final StringBuilder builder = new StringBuilder();
+        builder.append("broadcastBatteryChanged; ");
+        builder.append("force="); builder.append(forceUpdate);
+        builder.append(",seq="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_SEQUENCE, -1));
+        builder.append(",s="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_STATUS, -1));
+        builder.append(",h="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_HEALTH, -1));
+        builder.append(",p="); builder.append(intent.getBooleanExtra(
+                BatteryManager.EXTRA_PRESENT, false));
+        builder.append(",l="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_LEVEL, -1));
+        builder.append(",bl="); builder.append(intent.getBooleanExtra(
+                BatteryManager.EXTRA_BATTERY_LOW, false));
+        builder.append(",sc="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_SCALE, -1));
+        builder.append(",pt="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_PLUGGED, -1));
+        builder.append(",v="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_VOLTAGE, -1));
+        builder.append(",t="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_TEMPERATURE, -1));
+        builder.append(",tech="); builder.append(intent.getStringExtra(
+                BatteryManager.EXTRA_TECHNOLOGY));
+        builder.append(",invc="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_INVALID_CHARGER, -1));
+        builder.append(",mcc="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_MAX_CHARGING_CURRENT, -1));
+        builder.append(",mcv="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, -1));
+        builder.append(",chc="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_CHARGE_COUNTER, -1));
+        builder.append(",cc="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_CYCLE_COUNT, -1));
+        builder.append(",chs="); builder.append(intent.getIntExtra(
+                BatteryManager.EXTRA_CHARGING_STATUS, -1));
+
+        Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER, builder.toString());
+    }
+
     private void sendBatteryLevelChangedIntentLocked() {
         Bundle event = new Bundle();
         long now = SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 955b75d..3f4902d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1122,7 +1122,11 @@
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
             }
             final byte[] statsProto = bus.getStatsProto();
-
+            try {
+                bus.close();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failure close BatteryUsageStats", e);
+            }
             data.add(FrameworkStatsLog.buildStatsEvent(atomTag, statsProto));
 
             return StatsManager.PULL_SUCCESS;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 8f52f67..416c110 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -86,7 +86,6 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -2318,6 +2317,7 @@
                         Slog.d(TAG_AM, "Skipping freeze because process is marked "
                                 + "should not be frozen");
                     }
+                    reportProcessFreezableChangedLocked(proc);
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 3f54cfc..f0cc09f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1923,7 +1923,6 @@
         int procState;
         int capability = cycleReEval ? getInitialCapability(app) : 0;
 
-        boolean foregroundActivities = false;
         boolean hasVisibleActivities = false;
         if (app == topApp && PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
             // The last app on the list is the foreground app.
@@ -1937,7 +1936,6 @@
                 schedGroup = SCHED_GROUP_DEFAULT;
                 state.setAdjType("intermediate-top-activity");
             }
-            foregroundActivities = true;
             hasVisibleActivities = true;
             procState = PROCESS_STATE_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1988,7 +1986,6 @@
             adj = FOREGROUND_APP_ADJ;
             schedGroup = SCHED_GROUP_BACKGROUND;
             state.setAdjType("top-sleeping");
-            foregroundActivities = true;
             procState = PROCESS_STATE_CUR_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
@@ -2008,7 +2005,8 @@
             }
         }
 
-        // Examine all activities if not already foreground.
+        // Examine all non-top activities.
+        boolean foregroundActivities = app == topApp;
         if (!foregroundActivities && state.getCachedHasActivities()) {
             state.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
                     adj, foregroundActivities, hasVisibleActivities, procState, schedGroup,
@@ -2532,25 +2530,6 @@
             }
         }
 
-        state.setCurRawAdj(adj);
-        adj = psr.modifyRawOomAdj(adj);
-        if (adj > state.getMaxAdj()) {
-            adj = state.getMaxAdj();
-            if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
-                schedGroup = SCHED_GROUP_DEFAULT;
-            }
-        }
-
-        // Put bound foreground services in a special sched group for additional
-        // restrictions on screen off
-        if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
-                && !state.shouldScheduleLikeTopApp()) {
-            if (schedGroup > SCHED_GROUP_RESTRICTED) {
-                schedGroup = SCHED_GROUP_RESTRICTED;
-            }
-        }
-
         // apply capability from FGS.
         if (psr.hasForegroundServices()) {
             capability |= capabilityFromFGS;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e54e430..93b228f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3397,12 +3397,16 @@
         final boolean wasStopped = info.isStopped();
         // Check if we should mark the processrecord for first launch after force-stopping
         if (wasStopped) {
-            boolean wasEverLaunched;
+            boolean wasEverLaunched = false;
             if (android.app.Flags.useAppInfoNotLaunched()) {
                 wasEverLaunched = !info.isNotLaunched();
             } else {
-                wasEverLaunched = mService.getPackageManagerInternal()
-                        .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+                try {
+                    wasEverLaunched = mService.getPackageManagerInternal()
+                            .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+                } catch (IllegalArgumentException e) {
+                    // Package doesn't have state yet, assume not launched
+                }
             }
             // Check if the hosting record is for an activity or not. Since the stopped
             // state tracking is handled differently to avoid WM calling back into AM,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 99c3eca..439bca0 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -453,7 +453,9 @@
       proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
       proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
       proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
-      proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, isLocal
+                ? StorageRequestMessage.LOCAL_ON_REBOOT
+                : StorageRequestMessage.SERVER_ON_REBOOT);
       proto.end(msgToken);
       proto.end(msgsToken);
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 2a16872..dba6c33 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -50,7 +50,6 @@
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -74,6 +73,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
 import java.util.ArrayList;
@@ -203,7 +203,7 @@
          */
         @VisibleForTesting
         public String[] getFingerprintAidlInstances() {
-            return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+            return FingerprintService.getDeclaredInstances();
         }
 
         /**
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index ac3c028..b2c616a 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -316,6 +316,7 @@
         Pair<BiometricSensor, Integer> sensorNotEnrolled = null;
         Pair<BiometricSensor, Integer> sensorLockout = null;
         Pair<BiometricSensor, Integer> hardwareNotDetected = null;
+        Pair<BiometricSensor, Integer> biometricAppNotAllowed = null;
         for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) {
             final int status = pair.second;
             if (status == BIOMETRIC_LOCKOUT_TIMED || status == BIOMETRIC_LOCKOUT_PERMANENT) {
@@ -327,6 +328,9 @@
             if (status == BIOMETRIC_HARDWARE_NOT_DETECTED) {
                 hardwareNotDetected = pair;
             }
+            if (status == BIOMETRIC_NOT_ENABLED_FOR_APPS) {
+                biometricAppNotAllowed = pair;
+            }
         }
 
         // If there is a sensor locked out, prioritize lockout over other sensor's error.
@@ -339,6 +343,10 @@
             return hardwareNotDetected;
         }
 
+        if (Flags.mandatoryBiometrics() && biometricAppNotAllowed != null) {
+            return biometricAppNotAllowed;
+        }
+
         // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
         // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
         // BIOMETRIC_INSUFFICIENT_STRENGTH error.
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 8711214..407ef1e 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -321,6 +321,9 @@
             case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
                 biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
                 break;
+            case BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+                break;
             default:
                 Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
                 biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -384,9 +387,12 @@
                 return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
             case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR:
                 return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
+            case BIOMETRIC_NOT_ENABLED_FOR_APPS:
+                if (Flags.mandatoryBiometrics()) {
+                    return BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+                }
             case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
             case BIOMETRIC_HARDWARE_NOT_DETECTED:
-            case BIOMETRIC_NOT_ENABLED_FOR_APPS:
             default:
                 return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 60cfd5a..2f6ba0b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -26,6 +26,7 @@
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.fingerprint.FingerprintSensorConfigurations.getIFingerprint;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -78,6 +79,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -1015,7 +1017,7 @@
         this(context, BiometricContext.getInstance(context),
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
+                () -> getDeclaredInstances(),
                 null /* fingerprintProvider */,
                 null /* fingerprintProviderFunction */);
     }
@@ -1039,8 +1041,7 @@
         mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider :
                 (name) -> {
                     final String fqName = IFingerprint.DESCRIPTOR + "/" + name;
-                    final IFingerprint fp = IFingerprint.Stub.asInterface(
-                            Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+                    final IFingerprint fp = getIFingerprint(fqName);
                     if (fp != null) {
                         try {
                             return new FingerprintProvider(getContext(),
@@ -1129,6 +1130,24 @@
         publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
     }
 
+    /**
+     * Get all fingerprint hal instances declared in manifest
+     * @return instance names
+     */
+    public static String[] getDeclaredInstances() {
+        String[] a = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+        Slog.i(TAG, "Before:getDeclaredInstances: IFingerprint instance found, a.length="
+                + a.length);
+        if (!ArrayUtils.contains(a, "virtual")) {
+            // Now, the virtual hal is registered with IVirtualHal interface and it is also
+            //   moved from vendor to system_ext partition without a device manifest. So
+            //   if the old vhal is not declared, add here.
+            a = ArrayUtils.appendElement(String.class, a, "virtual");
+        }
+        Slog.i(TAG, "After:getDeclaredInstances: a.length=" + a.length);
+        return a;
+    }
+
     @NonNull
     private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
         final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index caa2c1c..fd3d996 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -20,9 +20,9 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.AcquiredInfoAndVendorCode;
-import android.hardware.biometrics.fingerprint.EnrollmentProgressStep;
-import android.hardware.biometrics.fingerprint.NextEnrollment;
+import android.hardware.biometrics.fingerprint.virtualhal.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.fingerprint.virtualhal.EnrollmentProgressStep;
+import android.hardware.biometrics.fingerprint.virtualhal.NextEnrollment;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintEnrollOptions;
 import android.hardware.fingerprint.FingerprintManager;
@@ -300,4 +300,4 @@
         super.getSensorId_enforcePermission();
         return mSensorId;
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 9edaa4e..8195efe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -17,6 +17,8 @@
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
 import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY;
+import static android.hardware.fingerprint.FingerprintSensorConfigurations.getIFingerprint;
+import static android.hardware.fingerprint.FingerprintSensorConfigurations.remapFqName;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,9 +36,9 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.IVirtualHal;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.virtualhal.IVirtualHal;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintEnrollOptions;
@@ -291,7 +293,8 @@
         if (mTestHalEnabled) {
             return true;
         }
-        return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)
+        return (ServiceManager.checkService(
+                remapFqName(IFingerprint.DESCRIPTOR + "/" + mHalInstanceName))
                 != null);
     }
 
@@ -330,10 +333,7 @@
 
         Slog.d(getTag(), "Daemon was null, reconnecting");
 
-        mDaemon = IFingerprint.Stub.asInterface(
-                Binder.allowBlocking(
-                        ServiceManager.waitForDeclaredService(
-                                IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent)));
+        mDaemon = getIFingerprint(IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent);
         if (mDaemon == null) {
             Slog.e(getTag(), "Unable to get daemon");
             return null;
@@ -905,8 +905,8 @@
     void setTestHalEnabled(boolean enabled) {
         final boolean changed = enabled != mTestHalEnabled;
         mTestHalEnabled = enabled;
-        Slog.i(getTag(), "setTestHalEnabled(): useVhalForTesting=" + Flags.useVhalForTesting()
-                + "mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+        Slog.i(getTag(), "setTestHalEnabled(): useVhalForTestingFlags=" + Flags.useVhalForTesting()
+                + " mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
         if (changed && useVhalForTesting()) {
             getHalInstance();
         }
@@ -999,12 +999,13 @@
      */
     public IVirtualHal getVhal() throws RemoteException {
         if (mVhal == null && useVhalForTesting()) {
-            mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
-            if (mVhal == null) {
-                Slog.e(getTag(), "Unable to get fingerprint virtualhal interface");
-            }
+            mVhal = IVirtualHal.Stub.asInterface(
+                    Binder.allowBlocking(
+                            ServiceManager.waitForService(
+                                    IVirtualHal.DESCRIPTOR + "/"
+                                            + mHalInstanceNameCurrent)));
+            Slog.d(getTag(), "getVhal " + mHalInstanceNameCurrent);
         }
-
         return mVhal;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d12d7b2..25d1fe7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.hardware.fingerprint.FingerprintSensorConfigurations.remapFqName;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -356,8 +358,8 @@
         if (mTestHalEnabled) {
             return true;
         }
-        return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
-                != null);
+        return (ServiceManager.checkService(
+                remapFqName(IFingerprint.DESCRIPTOR + "/" + halInstance)) != null);
     }
 
     @NonNull protected BiometricContext getBiometricContext() {
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 028b9b0..fcbcb02 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -32,8 +32,9 @@
     public BroadcastRadioService(Context context) {
         super(context);
         ArrayList<String> serviceNameList = IRadioServiceAidlImpl.getServicesNames();
-        mServiceImpl = serviceNameList.isEmpty() ? new IRadioServiceHidlImpl(this)
-                : new IRadioServiceAidlImpl(this, serviceNameList);
+        RadioServiceUserController userController = new RadioServiceUserControllerImpl();
+        mServiceImpl = serviceNameList.isEmpty() ? new IRadioServiceHidlImpl(this, userController)
+                : new IRadioServiceAidlImpl(this, serviceNameList, userController);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 16514fa..332958d 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -69,8 +69,9 @@
         return serviceList;
     }
 
-    IRadioServiceAidlImpl(BroadcastRadioService service, ArrayList<String> serviceList) {
-        this(service, new BroadcastRadioServiceImpl(serviceList));
+    IRadioServiceAidlImpl(BroadcastRadioService service, List<String> serviceList,
+            RadioServiceUserController userController) {
+        this(service, new BroadcastRadioServiceImpl(serviceList, userController));
         Slogf.i(TAG, "Initialize BroadcastRadioServiceAidl(%s)", service);
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index ab08342..67d3c95 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -59,13 +59,16 @@
     @GuardedBy("mLock")
     private final List<RadioManager.ModuleProperties> mV1Modules;
 
-    IRadioServiceHidlImpl(BroadcastRadioService service) {
+    IRadioServiceHidlImpl(BroadcastRadioService service,
+            RadioServiceUserController userController) {
         mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");
-        mHal1Client = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
+        Objects.requireNonNull(userController, "user controller cannot be null");
+        mHal1Client = new com.android.server.broadcastradio.hal1.BroadcastRadioService(
+                userController);
         mV1Modules = mHal1Client.loadModules();
         OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
         mHal2Client = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
-                max.isPresent() ? max.getAsInt() + 1 : 0);
+                max.isPresent() ? max.getAsInt() + 1 : 0, userController);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
index c705ebe..c15ccf1 100644
--- a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
+++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
@@ -16,19 +16,11 @@
 
 package com.android.server.broadcastradio;
 
-import android.app.ActivityManager;
-import android.os.Binder;
-import android.os.UserHandle;
-
 /**
- * Controller to handle users in {@link com.android.server.broadcastradio.BroadcastRadioService}
+ * Controller interface to handle users in
+ * {@link com.android.server.broadcastradio.BroadcastRadioService}
  */
-public final class RadioServiceUserController {
-
-    private RadioServiceUserController() {
-        throw new UnsupportedOperationException(
-                "RadioServiceUserController class is noninstantiable");
-    }
+public interface RadioServiceUserController {
 
     /**
      * Check if the user calling the method in Broadcast Radio Service is the current user or the
@@ -37,26 +29,20 @@
      * @return {@code true} if the user calling this method is the current user of system user,
      * {@code false} otherwise.
      */
-    public static boolean isCurrentOrSystemUser() {
-        int callingUser = Binder.getCallingUserHandle().getIdentifier();
-        return callingUser == getCurrentUser() || callingUser == UserHandle.USER_SYSTEM;
-    }
+    boolean isCurrentOrSystemUser();
 
     /**
      * Get current foreground user for Broadcast Radio Service
      *
      * @return foreground user id.
      */
-    public static int getCurrentUser() {
-        int userId = UserHandle.USER_NULL;
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            userId = ActivityManager.getCurrentUser();
-        } catch (RuntimeException e) {
-            // Activity manager not running, nothing we can do assume user 0.
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        return userId;
-    }
-}
+    int getCurrentUser();
+
+    /**
+     * Get id of the user handle assigned to the process that sent the binder transaction that is
+     * being processed
+     *
+     * @return Id of the user handle
+     */
+    int getCallingUserId();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserControllerImpl.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserControllerImpl.java
new file mode 100644
index 0000000..e305d20
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserControllerImpl.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.UserHandle;
+
+/**
+ * Implementation for the controller to handle users in
+ * {@link com.android.server.broadcastradio.BroadcastRadioService}
+ */
+public final class RadioServiceUserControllerImpl implements RadioServiceUserController {
+
+    /**
+     * @see RadioServiceUserController#isCurrentOrSystemUser()
+     */
+    @Override
+    public boolean isCurrentOrSystemUser() {
+        int callingUser = getCallingUserId();
+        return callingUser == getCurrentUser() || callingUser == UserHandle.USER_SYSTEM;
+    }
+
+    /**
+     * @see RadioServiceUserController#getCurrentUser()
+     */
+    @Override
+    public int getCurrentUser() {
+        int userId = UserHandle.USER_NULL;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            userId = ActivityManager.getCurrentUser();
+        } catch (RuntimeException e) {
+            // Activity manager not running, nothing we can do assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return userId;
+    }
+
+    /**
+     * @see RadioServiceUserController#getCallingUserId()
+     */
+    @Override
+    public int getCallingUserId() {
+        return Binder.getCallingUserHandle().getIdentifier();
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 7b50465..06024b5 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -51,6 +51,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Object mLock = new Object();
+    private final RadioServiceUserController mUserController;
 
     @GuardedBy("mLock")
     private int mNextModuleId;
@@ -77,7 +78,7 @@
                 }
 
                 RadioModule radioModule =
-                        RadioModule.tryLoadingModule(moduleId, name, newBinder);
+                        RadioModule.tryLoadingModule(moduleId, name, newBinder, mUserController);
                 if (radioModule == null) {
                     Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
                     return;
@@ -141,9 +142,12 @@
      * BroadcastRadio HAL services
      *
      * @param serviceNameList list of names of AIDL BroadcastRadio HAL services
+     * @param userController User controller implementation
      */
-    public BroadcastRadioServiceImpl(ArrayList<String> serviceNameList) {
+    public BroadcastRadioServiceImpl(List<String> serviceNameList,
+            RadioServiceUserController userController) {
         mNextModuleId = 0;
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
         if (DEBUG) {
             Slogf.d(TAG, "Initializing BroadcastRadioServiceImpl %s", IBroadcastRadio.DESCRIPTOR);
         }
@@ -202,7 +206,7 @@
         if (DEBUG) {
             Slogf.d(TAG, "Open AIDL radio session");
         }
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.e(TAG, "Cannot open tuner on AIDL HAL client for non-current user");
             throw new IllegalStateException("Cannot open session for non-current user");
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index a176a32..20ee49e 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -62,6 +62,7 @@
     private final Handler mHandler;
     private final RadioEventLogger mLogger;
     private final RadioManager.ModuleProperties mProperties;
+    private final RadioServiceUserController mUserController;
 
     /**
      * Tracks antenna state reported by HAL (if any).
@@ -194,15 +195,18 @@
     };
 
     @VisibleForTesting
-    RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties) {
+    RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties,
+            RadioServiceUserController userController) {
         mProperties = Objects.requireNonNull(properties, "properties cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
         mHandler = new Handler(Looper.getMainLooper());
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
         mLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     @Nullable
-    static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder) {
+    static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder,
+            RadioServiceUserController userController) {
         try {
             Slogf.i(TAG, "Try loading module for module id = %d, module name = %s",
                     moduleId, moduleName);
@@ -232,7 +236,7 @@
             RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties(
                     moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig);
 
-            return new RadioModule(service, prop);
+            return new RadioModule(service, prop, userController);
         } catch (RemoteException ex) {
             Slogf.e(TAG, ex, "Failed to load module %s", moduleName);
             return null;
@@ -256,7 +260,7 @@
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
             boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
-            tunerSession = new TunerSession(this, mService, userCb);
+            tunerSession = new TunerSession(this, mService, userCb, mUserController);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
@@ -440,7 +444,7 @@
 
     @GuardedBy("mLock")
     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
-        int currentUserId = RadioServiceUserController.getCurrentUser();
+        int currentUserId = mUserController.getCurrentUser();
         List<TunerSession> deadSessions = null;
         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
             if (mAidlTunerSessions.valueAt(i).mUserId != currentUserId
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index e90a1dd..f22661b 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -52,6 +52,7 @@
     final android.hardware.radio.ITunerCallback mCallback;
     private final int mUid;
     private final IBroadcastRadio mService;
+    private final RadioServiceUserController mUserController;
 
     @GuardedBy("mLock")
     private boolean mIsClosed;
@@ -65,11 +66,13 @@
     private RadioManager.BandConfig mPlaceHolderConfig;
 
     TunerSession(RadioModule radioModule, IBroadcastRadio service,
-            android.hardware.radio.ITunerCallback callback) {
+            android.hardware.radio.ITunerCallback callback,
+            RadioServiceUserController userController) {
         mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
-        mUserId = Binder.getCallingUserHandle().getIdentifier();
         mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
+        mUserId = mUserController.getCallingUserId();
         mUid = Binder.getCallingUid();
         mLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
@@ -126,7 +129,7 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set configuration for AIDL HAL client from non-current user");
             return;
         }
@@ -169,7 +172,7 @@
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot step on AIDL HAL client from non-current user");
             return;
         }
@@ -187,7 +190,7 @@
     public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user");
             return;
         }
@@ -204,7 +207,7 @@
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
         mLogger.logRadioEvent("Tune with selector %s", selector);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot tune on AIDL HAL client from non-current user");
             return;
         }
@@ -226,7 +229,7 @@
     @Override
     public void cancel() {
         Slogf.i(TAG, "Cancel");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot cancel on AIDL HAL client from non-current user");
             return;
         }
@@ -255,7 +258,7 @@
     @Override
     public boolean startBackgroundScan() {
         Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
             return false;
         }
@@ -268,7 +271,7 @@
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
         mLogger.logRadioEvent("Start programList updates %s", filter);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start program list updates on AIDL HAL client from non-current user");
             return;
@@ -344,7 +347,7 @@
     @Override
     public void stopProgramListUpdates() throws RemoteException {
         mLogger.logRadioEvent("Stop programList updates");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot stop program list updates on AIDL HAL client from non-current user");
             return;
@@ -389,7 +392,7 @@
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
         mLogger.logRadioEvent("set ConfigFlag %s to %b ",
                 ConfigFlag.$.toString(flag), value);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set config flag for AIDL HAL client from non-current user");
             return;
         }
@@ -406,7 +409,7 @@
     @Override
     public Map<String, String> setParameters(Map<String, String> parameters) {
         mLogger.logRadioEvent("Set parameters ");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set parameters for AIDL HAL client from non-current user");
             return new ArrayMap<>();
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
index fb42c94..6a6a3ae 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
@@ -35,6 +35,7 @@
      * This field is used by native code, do not access or modify.
      */
     private final long mNativeContext = nativeInit();
+    private final RadioServiceUserController mUserController;
 
     private final Object mLock = new Object();
 
@@ -50,6 +51,10 @@
     private native Tuner nativeOpenTuner(long nativeContext, int moduleId,
             RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback);
 
+    public BroadcastRadioService(RadioServiceUserController userController) {
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
+    }
+
     public @NonNull List<RadioManager.ModuleProperties> loadModules() {
         synchronized (mLock) {
             return Objects.requireNonNull(nativeLoadModules(mNativeContext));
@@ -58,7 +63,7 @@
 
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
             boolean withAudio, ITunerCallback callback) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.e(TAG, "Cannot open tuner on HAL 1.x client for non-current user");
             throw new IllegalStateException("Cannot open tuner for non-current user");
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index 7cac409..8e64600d2 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 
 import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.broadcastradio.RadioServiceUserControllerImpl;
 import com.android.server.utils.Slogf;
 
 import java.util.List;
@@ -51,6 +52,7 @@
     private boolean mIsMuted = false;
     private int mRegion;
     private final boolean mWithAudio;
+    private final RadioServiceUserController mUserController = new RadioServiceUserControllerImpl();
 
     Tuner(@NonNull ITunerCallback clientCallback, int halRev,
             int region, boolean withAudio, int band) {
@@ -127,7 +129,7 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set configuration for HAL 1.x client from non-current user");
             return;
         }
@@ -176,7 +178,7 @@
 
     @Override
     public void step(boolean directionDown, boolean skipSubChannel) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot step on HAL 1.x client from non-current user");
             return;
         }
@@ -189,7 +191,7 @@
 
     @Override
     public void seek(boolean directionDown, boolean skipSubChannel) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user");
             return;
         }
@@ -202,7 +204,7 @@
 
     @Override
     public void tune(ProgramSelector selector) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot tune on HAL 1.x client from non-current user");
             return;
         }
@@ -219,7 +221,7 @@
 
     @Override
     public void cancel() {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot cancel on HAL 1.x client from non-current user");
             return;
         }
@@ -231,7 +233,7 @@
 
     @Override
     public void cancelAnnouncement() {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot cancel announcement on HAL 1.x client from non-current user");
             return;
         }
@@ -260,7 +262,7 @@
 
     @Override
     public boolean startBackgroundScan() {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start background scan on HAL 1.x client from non-current user");
             return false;
@@ -285,7 +287,7 @@
 
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start program list updates on HAL 1.x client from non-current user");
             return;
@@ -295,7 +297,7 @@
 
     @Override
     public void stopProgramListUpdates() {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot stop program list updates on HAL 1.x client from non-current user");
             return;
@@ -321,7 +323,7 @@
 
     @Override
     public void setConfigFlag(int flag, boolean value) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set config flag for HAL 1.x client from non-current user");
             return;
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index a4efa2e..3227afd 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -50,6 +50,8 @@
 
     private final Object mLock = new Object();
 
+    private final RadioServiceUserController mUserController;
+
     @GuardedBy("mLock")
     private int mNextModuleId;
 
@@ -75,7 +77,8 @@
                     moduleId = mNextModuleId;
                 }
 
-                RadioModule radioModule = RadioModule.tryLoadingModule(moduleId, serviceName);
+                RadioModule radioModule = RadioModule.tryLoadingModule(moduleId, serviceName,
+                        mUserController);
                 if (radioModule == null) {
                     return;
                 }
@@ -123,8 +126,9 @@
         }
     };
 
-    public BroadcastRadioService(int nextModuleId) {
+    public BroadcastRadioService(int nextModuleId, RadioServiceUserController userController) {
         mNextModuleId = nextModuleId;
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
         try {
             IServiceManager manager = IServiceManager.getService();
             if (manager == null) {
@@ -138,8 +142,10 @@
     }
 
     @VisibleForTesting
-    BroadcastRadioService(int nextModuleId, IServiceManager manager) {
+    BroadcastRadioService(int nextModuleId, IServiceManager manager,
+            RadioServiceUserController userController) {
         mNextModuleId = nextModuleId;
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
         Objects.requireNonNull(manager, "Service manager cannot be null");
         try {
             manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
@@ -171,7 +177,7 @@
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
             boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
         Slogf.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
             throw new IllegalStateException("Cannot open session for non-current user");
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index d3b2448..a0d6cc2 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -66,6 +66,7 @@
     private final Object mLock = new Object();
     private final Handler mHandler;
     private final RadioEventLogger mEventLogger;
+    private final RadioServiceUserController mUserController;
 
     @GuardedBy("mLock")
     private ITunerSession mHalTunerSession;
@@ -148,16 +149,18 @@
     private final Set<TunerSession> mAidlTunerSessions = new ArraySet<>();
 
     @VisibleForTesting
-    RadioModule(@NonNull IBroadcastRadio service,
-            @NonNull RadioManager.ModuleProperties properties) {
+    RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties,
+            RadioServiceUserController userController) {
         mProperties = Objects.requireNonNull(properties);
         mService = Objects.requireNonNull(service);
         mHandler = new Handler(Looper.getMainLooper());
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
         mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     @Nullable
-    static RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
+    static RadioModule tryLoadingModule(int idx, String fqName,
+            RadioServiceUserController controller) {
         try {
             Slogf.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
@@ -179,7 +182,7 @@
             RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
                     service.getProperties(), amfmConfig.value, dabConfig.value);
 
-            return new RadioModule(service, prop);
+            return new RadioModule(service, prop, controller);
         } catch (RemoteException ex) {
             Slogf.e(TAG, "Failed to load module " + fqName, ex);
             return null;
@@ -208,7 +211,8 @@
                 });
                 mHalTunerSession = Objects.requireNonNull(hwSession.value);
             }
-            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
+            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb,
+                    mUserController);
             mAidlTunerSessions.add(tunerSession);
 
             // Propagate state to new client. Note: These callbacks are invoked while holding mLock
@@ -375,7 +379,7 @@
 
     @GuardedBy("mLock")
     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
-        int currentUserId = RadioServiceUserController.getCurrentUser();
+        int currentUserId = mUserController.getCurrentUser();
         List<TunerSession> deadSessions = null;
         for (TunerSession tunerSession : mAidlTunerSessions) {
             if (tunerSession.mUserId != currentUserId && tunerSession.mUserId
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 80efacd..dc164b1 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -16,7 +16,6 @@
 
 package com.android.server.broadcastradio.hal2;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.hardware.broadcastradio.V2_0.ConfigFlag;
@@ -27,7 +26,6 @@
 import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
-import android.os.Binder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -55,6 +53,7 @@
     private final ITunerSession mHwSession;
     final int mUserId;
     final android.hardware.radio.ITunerCallback mCallback;
+    private final RadioServiceUserController mUserController;
 
     @GuardedBy("mLock")
     private boolean mIsClosed = false;
@@ -66,12 +65,14 @@
     // necessary only for older APIs compatibility
     private RadioManager.BandConfig mDummyConfig = null;
 
-    TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
-            @NonNull android.hardware.radio.ITunerCallback callback) {
+    TunerSession(RadioModule module, ITunerSession hwSession,
+            android.hardware.radio.ITunerCallback callback,
+            RadioServiceUserController userController) {
         mModule = Objects.requireNonNull(module);
         mHwSession = Objects.requireNonNull(hwSession);
-        mUserId = Binder.getCallingUserHandle().getIdentifier();
         mCallback = Objects.requireNonNull(callback);
+        mUserController = Objects.requireNonNull(userController, "User controller can not be null");
+        mUserId = mUserController.getCallingUserId();
         mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
@@ -120,7 +121,7 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set configuration for HAL 2.0 client from non-current user");
             return;
         }
@@ -162,7 +163,7 @@
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot step on HAL 2.0 client from non-current user");
             return;
         }
@@ -177,7 +178,7 @@
     public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mEventLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user");
             return;
         }
@@ -191,7 +192,7 @@
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
         mEventLogger.logRadioEvent("Tune with selector %s", selector);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot tune on HAL 2.0 client from non-current user");
             return;
         }
@@ -205,7 +206,7 @@
     @Override
     public void cancel() {
         Slogf.i(TAG, "Cancel");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot cancel on HAL 2.0 client from non-current user");
             return;
         }
@@ -230,7 +231,7 @@
     @Override
     public boolean startBackgroundScan() {
         Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start background scan on HAL 2.0 client from non-current user");
             return false;
@@ -242,7 +243,7 @@
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) {
         mEventLogger.logRadioEvent("start programList updates %s", filter);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start program list updates on HAL 2.0 client from non-current user");
             return;
@@ -306,7 +307,7 @@
     @Override
     public void stopProgramListUpdates() throws RemoteException {
         mEventLogger.logRadioEvent("Stop programList updates");
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot stop program list updates on HAL 2.0 client from non-current user");
             return;
@@ -355,7 +356,7 @@
     @Override
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
         mEventLogger.logRadioEvent("Set ConfigFlag  %s = %b", ConfigFlag.toString(flag), value);
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set config flag for HAL 2.0 client from non-current user");
             return;
         }
@@ -368,7 +369,7 @@
 
     @Override
     public Map<String, String> setParameters(Map<String, String> parameters) {
-        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+        if (!mUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot set parameters for HAL 2.0 client from non-current user");
             return new ArrayMap<>();
         }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 17835b2..05fc6bc 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -379,12 +379,8 @@
                 streamCount = mStreamStats.size();
             }
             if (CameraServiceProxy.DEBUG) {
-                String ultrawideDebug = Flags.logUltrawideUsage()
-                        ? ", wideAngleUsage " + mUsedUltraWide
-                        : "";
-                String zoomOverrideDebug = Flags.logZoomOverrideUsage()
-                        ? ", zoomOverrideUsage " + mUsedZoomOverride
-                        : "";
+                String ultrawideDebug = ", wideAngleUsage " + mUsedUltraWide;
+                String zoomOverrideDebug = ", zoomOverrideUsage " + mUsedZoomOverride;
                 String mostRequestedFpsRangeDebug = Flags.analytics24q3()
                         ? ", mostRequestedFpsRange " + mMostRequestedFpsRange
                         : "";
@@ -1338,9 +1334,8 @@
         List<CameraStreamStats> streamStats = cameraState.getStreamStats();
         String userTag = cameraState.getUserTag();
         int videoStabilizationMode = cameraState.getVideoStabilizationMode();
-        boolean usedUltraWide = Flags.logUltrawideUsage() ? cameraState.getUsedUltraWide() : false;
-        boolean usedZoomOverride =
-                Flags.logZoomOverrideUsage() ? cameraState.getUsedZoomOverride() : false;
+        boolean usedUltraWide = cameraState.getUsedUltraWide();
+        boolean usedZoomOverride = cameraState.getUsedZoomOverride();
         long logId = cameraState.getLogId();
         int sessionIdx = cameraState.getSessionIndex();
         CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 8a3e392..1d68ee54 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.annotation.Nullable;
 import android.hardware.display.BrightnessInfo;
 import android.os.Handler;
 import android.os.IBinder;
@@ -92,7 +93,7 @@
         return mHbmController.getNormalBrightnessMax();
     }
 
-    void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token,
+    void loadFromConfig(@Nullable HighBrightnessModeMetadata hbmMetadata, IBinder token,
             DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) {
         applyChanges(
                 () -> mNormalBrightnessModeController.resetNbmData(
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ed16b14..9644b1d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2170,9 +2170,7 @@
 
             HighBrightnessModeMetadata hbmMetadata =
                     mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-            if (hbmMetadata != null) {
-                dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
-            }
+            dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
         }
     }
 
@@ -2291,9 +2289,7 @@
 
             HighBrightnessModeMetadata hbmMetadata =
                     mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-            if (hbmMetadata != null) {
-                dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
-            }
+            dpc.onDisplayChanged(hbmMetadata, leadDisplayId);
         }
     }
 
@@ -3560,6 +3556,18 @@
         DisplayManagerFlags getFlags() {
             return new DisplayManagerFlags();
         }
+
+        DisplayPowerController getDisplayPowerController(Context context,
+                DisplayPowerController.Injector injector,
+                DisplayManagerInternal.DisplayPowerCallbacks callbacks, Handler handler,
+                SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
+                BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
+                Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
+                boolean bootCompleted, DisplayManagerFlags flags) {
+            return new DisplayPowerController(context, injector, callbacks, handler, sensorManager,
+                    blanker, logicalDisplay, brightnessTracker, brightnessSetting,
+                    onBrightnessChangeRunnable, hbmMetadata, bootCompleted, flags);
+        }
     }
 
     @VisibleForTesting
@@ -3612,7 +3620,7 @@
         // with the corresponding displaydevice.
         HighBrightnessModeMetadata hbmMetadata =
                 mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-        displayPowerController = new DisplayPowerController(
+        displayPowerController = mInjector.getDisplayPowerController(
                 mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                 mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
                 () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7c591e3..c3faec0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -849,7 +849,8 @@
      *
      * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
      */
-    public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
+    public void onDisplayChanged(@Nullable HighBrightnessModeMetadata hbmMetadata,
+            int leadDisplayId) {
         mLeadDisplayId = leadDisplayId;
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         if (device == null) {
@@ -949,7 +950,7 @@
     }
 
     private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
-            HighBrightnessModeMetadata hbmMetadata) {
+            @Nullable HighBrightnessModeMetadata hbmMetadata) {
         // All properties that depend on the associated DisplayDevice and the DDC must be
         // updated here.
         mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index da9eef2..135cab6 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -266,7 +266,7 @@
         mSettingsObserver.stopObserving();
     }
 
-    void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
+    void setHighBrightnessModeMetadata(@Nullable HighBrightnessModeMetadata hbmInfo) {
         mHighBrightnessModeMetadata = hbmInfo;
     }
 
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 649678c..69ba785 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -56,3 +56,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "trace_battery_changed_broadcast_event"
+    description: "Add tracing to record battery changed broadcast event"
+    bug: "365410144"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 81204ef..a8d5696 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -927,12 +927,14 @@
     protected int handleVendorCommandWithId(HdmiCecMessage message) {
         byte[] params = message.getParams();
         int vendorId = HdmiUtils.threeBytesToInt(params);
-        if (message.getDestination() == Constants.ADDR_BROADCAST
-                || message.getSource() == Constants.ADDR_UNREGISTERED) {
-            Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
-        } else if (!mService.invokeVendorCommandListenersOnReceived(
+        if (!mService.invokeVendorCommandListenersOnReceived(
                 mDeviceType, message.getSource(), message.getDestination(), params, true)) {
-            return Constants.ABORT_REFUSED;
+            if (message.getDestination() == Constants.ADDR_BROADCAST
+                    || message.getSource() == Constants.ADDR_UNREGISTERED) {
+                Slog.v(TAG, "Broadcast vendor command with no listeners. Ignoring");
+            } else {
+                return Constants.ABORT_REFUSED;
+            }
         }
         return Constants.HANDLED;
     }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1285a61..dfcea9f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2269,14 +2269,8 @@
     // Native callback.
     @SuppressWarnings("unused")
     private void notifyTouchpadHardwareState(TouchpadHardwareState hardwareStates, int deviceId) {
-        Slog.d(TAG, "notifyTouchpadHardwareState: Time: "
-                + hardwareStates.getTimestamp() + ", No. Buttons: "
-                + hardwareStates.getButtonsDown() + ", No. Fingers: "
-                + hardwareStates.getFingerCount() + ", No. Touch: "
-                + hardwareStates.getTouchCount() + ", Id: "
-                + deviceId);
         if (mTouchpadDebugViewController != null) {
-            mTouchpadDebugViewController.updateTouchpadHardwareState(hardwareStates);
+            mTouchpadDebugViewController.updateTouchpadHardwareState(hardwareStates, deviceId);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index ba56ad0..7d8ce5f 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -22,6 +22,7 @@
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -65,7 +66,7 @@
         mTouchpadId = touchpadId;
         mWindowManager =
                 Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
-        init(context);
+        init(context, touchpadId);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
 
         // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
@@ -88,32 +89,40 @@
         mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
     }
 
-    private void init(Context context) {
+    private void init(Context context, int touchpadId) {
         setOrientation(VERTICAL);
         setLayoutParams(new LayoutParams(
                 LayoutParams.WRAP_CONTENT,
                 LayoutParams.WRAP_CONTENT));
-        setBackgroundColor(Color.RED);
+        setBackgroundColor(Color.TRANSPARENT);
 
-        // TODO(b/286551975): Replace this content with the touchpad debug view.
-        TextView textView1 = new TextView(context);
-        textView1.setBackgroundColor(Color.TRANSPARENT);
-        textView1.setTextSize(20);
-        textView1.setText("Touchpad Debug View 1");
-        textView1.setGravity(Gravity.CENTER);
-        textView1.setTextColor(Color.WHITE);
-        textView1.setLayoutParams(new LayoutParams(1000, 200));
+        TextView nameView = new TextView(context);
+        nameView.setBackgroundColor(Color.RED);
+        nameView.setTextSize(20);
+        nameView.setText(Objects.requireNonNull(Objects.requireNonNull(
+                        mContext.getSystemService(InputManager.class))
+                .getInputDevice(touchpadId)).getName());
+        nameView.setGravity(Gravity.CENTER);
+        nameView.setTextColor(Color.WHITE);
+        nameView.setLayoutParams(new LayoutParams(1000, 200));
 
-        TextView textView2 = new TextView(context);
-        textView2.setBackgroundColor(Color.TRANSPARENT);
-        textView2.setTextSize(20);
-        textView2.setText("Touchpad Debug View 2");
-        textView2.setGravity(Gravity.CENTER);
-        textView2.setTextColor(Color.WHITE);
-        textView2.setLayoutParams(new LayoutParams(1000, 200));
+        TouchpadVisualisationView touchpadVisualisationView =
+                new TouchpadVisualisationView(context);
+        touchpadVisualisationView.setBackgroundColor(Color.WHITE);
+        touchpadVisualisationView.setLayoutParams(new LayoutParams(1000, 200));
 
-        addView(textView1);
-        addView(textView2);
+        //TODO(b/365562952): Add a display for recognized gesture info here
+        TextView gestureInfoView = new TextView(context);
+        gestureInfoView.setBackgroundColor(Color.GRAY);
+        gestureInfoView.setTextSize(20);
+        gestureInfoView.setText("Touchpad Debug View 3");
+        gestureInfoView.setGravity(Gravity.CENTER);
+        gestureInfoView.setTextColor(Color.BLACK);
+        gestureInfoView.setLayoutParams(new LayoutParams(1000, 200));
+
+        addView(nameView);
+        addView(touchpadVisualisationView);
+        addView(gestureInfoView);
 
         updateScreenDimensions();
     }
@@ -204,7 +213,15 @@
         return mWindowLayoutParams;
     }
 
-    public void updateHardwareState(TouchpadHardwareState touchpadHardwareState) {
+    /**
+     * Notify the view of a change in TouchpadHardwareState and changing the
+     * color of the view based on the status of the button click.
+     */
+    public void updateHardwareState(TouchpadHardwareState touchpadHardwareState, int deviceId) {
+        if (deviceId != mTouchpadId) {
+            return;
+        }
+
         if (mLastTouchpadState.getButtonsDown() == 0) {
             if (touchpadHardwareState.getButtonsDown() > 0) {
                 onTouchpadButtonPress();
@@ -219,18 +236,11 @@
 
     private void onTouchpadButtonPress() {
         Slog.d("TouchpadDebugView", "You clicked me!");
-
-        // Iterate through all child views
-        // Temporary demonstration for testing
-        for (int i = 0; i < getChildCount(); i++) {
-            getChildAt(i).setBackgroundColor(Color.BLUE);
-        }
+        getChildAt(0).setBackgroundColor(Color.BLUE);
     }
 
     private void onTouchpadButtonRelease() {
         Slog.d("TouchpadDebugView", "You released the click");
-        for (int i = 0; i < getChildCount(); i++) {
-            getChildAt(i).setBackgroundColor(Color.RED);
-        }
+        getChildAt(0).setBackgroundColor(Color.RED);
     }
 }
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index bc53c49..4d527bd 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -68,6 +68,13 @@
     @Override
     public void onInputDeviceRemoved(int deviceId) {
         hideDebugView(deviceId);
+        if (mTouchpadDebugView == null) {
+            final InputManager inputManager = Objects.requireNonNull(
+                    mContext.getSystemService(InputManager.class));
+            for (int id : inputManager.getInputDeviceIds()) {
+                onInputDeviceAdded(id);
+            }
+        }
     }
 
     @Override
@@ -134,9 +141,13 @@
         Slog.d(TAG, "Touchpad debug view removed.");
     }
 
-    public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState) {
+    /**
+     * Notify the TouchpadDebugView with the new TouchpadHardwareState.
+     */
+    public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
+                                            int deviceId) {
         if (mTouchpadDebugView != null) {
-            mTouchpadDebugView.updateHardwareState(touchpadHardwareState);
+            mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java
new file mode 100644
index 0000000..38442bc
--- /dev/null
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input.debug;
+
+import android.content.Context;
+import android.view.View;
+
+public class TouchpadVisualisationView extends View {
+    public TouchpadVisualisationView(Context context) {
+        super(context);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 38ef5b8..7d44ba1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -889,8 +889,14 @@
                 // Hide notification first, as tie profile lock takes time
                 hideEncryptionNotification(new UserHandle(userId));
 
-                if (isCredentialSharableWithParent(userId)) {
-                    tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
+                if (android.app.admin.flags.Flags.fixRaceConditionInTieProfileLock()) {
+                    synchronized (mSpManager) {
+                        tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
+                    }
+                } else {
+                    if (isCredentialSharableWithParent(userId)) {
+                        tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
+                    }
                 }
             }
         });
@@ -1287,7 +1293,13 @@
                 mStorage.removeChildProfileLock(userId);
                 removeKeystoreProfileKey(userId);
             } else {
-                tieProfileLockIfNecessary(userId, profileUserPassword);
+                if (android.app.admin.flags.Flags.fixRaceConditionInTieProfileLock()) {
+                    synchronized (mSpManager) {
+                        tieProfileLockIfNecessary(userId, profileUserPassword);
+                    }
+                } else {
+                    tieProfileLockIfNecessary(userId, profileUserPassword);
+                }
             }
         } catch (IllegalStateException e) {
             setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, old, userId);
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
index 5b07cd9..688dbe9 100644
--- a/services/core/java/com/android/server/logcat/TEST_MAPPING
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -1,15 +1,12 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests_android_server_logcat_Presubmit"
+      "name": "FrameworksServicesTests_android_server_logcat"
     }
   ],
   "postsubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.logcat"}
-      ]
+      "name": "FrameworksServicesTests_android_server_logcat"
     }
   ]
 }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 3523a33..03fc60c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1094,7 +1094,7 @@
             return info;
         }
         throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
-                + service + " " + service.getClass());
+                + service.asBinder() + " " + service.getClass());
     }
 
     public boolean isSameUser(IInterface service, int userId) {
@@ -1585,6 +1585,9 @@
         // after the rebind delay
         if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
             registerService(cn, userId);
+        } else {
+            if (DEBUG) Slog.v(TAG, "skipped reregisterService cn=" + cn + " u=" + userId
+                    + " because of isPackageOrComponentAllowedWithPermission check");
         }
     }
 
@@ -1918,6 +1921,7 @@
                     .append(",targetSdkVersion=").append(targetSdkVersion)
                     .append(",connection=").append(connection == null ? null : "<connection>")
                     .append(",service=").append(service)
+                    .append(",serviceAsBinder=").append(service != null ? service.asBinder() : null)
                     .append(']').toString();
         }
 
@@ -1956,7 +1960,7 @@
 
         @Override
         public void binderDied() {
-            if (DEBUG) Slog.d(TAG, "binderDied");
+            if (DEBUG) Slog.d(TAG, "binderDied " + this);
             // Remove the service, but don't unbind from the service. The system will bring the
             // service back up, and the onServiceConnected handler will read the service with the
             // new binding. If this isn't a bound service, and is just a registered
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e541246..b9f0968 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -330,10 +330,19 @@
         }
 
         final long[] vibrationPattern = channel.getVibrationPattern();
-        if (vibrationPattern == null) {
-            return helper.createDefaultVibration(insistent);
+        if (vibrationPattern != null) {
+            return helper.createWaveformVibration(vibrationPattern, insistent);
         }
-        return helper.createWaveformVibration(vibrationPattern, insistent);
+
+        if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) {
+            final VibrationEffect vibrationEffectFromSoundUri =
+                    helper.createVibrationEffectFromSoundUri(channel.getSound());
+            if (vibrationEffectFromSoundUri != null) {
+                return vibrationEffectFromSoundUri;
+            }
+        }
+
+        return helper.createDefaultVibration(insistent);
     }
 
     private VibrationEffect calculateVibration() {
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 8a0e595..fbe7772 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -24,6 +24,9 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.media.AudioAttributes;
+import android.media.RingtoneManager;
+import android.media.Utils;
+import android.net.Uri;
 import android.os.Process;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
@@ -51,6 +54,7 @@
     @Nullable private final float[] mDefaultPwlePattern;
     @Nullable private final float[] mFallbackPwlePattern;
     private final int mDefaultVibrationAmplitude;
+    private final Context mContext;
 
     public VibratorHelper(Context context) {
         mVibrator = context.getSystemService(Vibrator.class);
@@ -68,6 +72,7 @@
                 com.android.internal.R.array.config_notificationFallbackVibeWaveform);
         mDefaultVibrationAmplitude = context.getResources().getInteger(
             com.android.internal.R.integer.config_defaultVibrationAmplitude);
+        mContext = context;
     }
 
     /**
@@ -184,6 +189,16 @@
      * @param insistent {@code true} if the vibration should loop until it is cancelled.
      */
     public VibrationEffect createDefaultVibration(boolean insistent) {
+        if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) {
+            final Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                    RingtoneManager.TYPE_NOTIFICATION);
+            final VibrationEffect vibrationEffectFromSoundUri =
+                    createVibrationEffectFromSoundUri(defaultRingtoneUri);
+            if (vibrationEffectFromSoundUri != null) {
+                return vibrationEffectFromSoundUri;
+            }
+        }
+
         if (mVibrator.hasFrequencyControl()) {
             VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent);
             if (effect != null) {
@@ -193,6 +208,22 @@
         return createWaveformVibration(mDefaultPattern, insistent);
     }
 
+    /**
+     * Safely create a {@link VibrationEffect} from given an uri {@code Uri}.
+     * with query parameter "vibration_uri"
+     *
+     * Use this function if the {@code Uri} is with a query parameter "vibration_uri" and the
+     * vibration_uri represents a valid vibration effect in xml
+     *
+     * @param uri {@code Uri} an uri including query parameter "vibraiton_uri"
+     */
+    public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+    }
+
     /** Returns if a given vibration can be played by the vibrator that does notification buzz. */
     public boolean areEffectComponentsSupported(VibrationEffect effect) {
         return mVibrator.areVibrationFeaturesSupported(effect);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index aac2c40..be3adc1 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -156,3 +156,10 @@
   description: "This flag enables forced auto-grouping conversations"
   bug: "336488844"
 }
+
+flag {
+  name: "notification_vibration_in_sound_uri"
+  namespace: "systemui"
+  description: "This flag enables sound uri with vibration source"
+  bug: "358524009"
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6303ecd..a41675a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -298,12 +298,13 @@
 
             restoreSettings();
 
-            // Wipe all shell overlays on boot, to recover from a potentially broken device
-            String shellPkgName = TextUtils.emptyIfNull(
-                    getContext().getString(android.R.string.config_systemShell));
-            mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated
-                    && shellPkgName.equals(overlayInfo.packageName));
-
+            if (Build.IS_USER) {
+                // Wipe all shell overlays on boot, to recover from a potentially broken device
+                String shellPkgName = TextUtils.emptyIfNull(
+                        getContext().getString(android.R.string.config_systemShell));
+                mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated
+                        && shellPkgName.equals(overlayInfo.packageName));
+            }
             initIfNeeded();
             onStartUser(UserHandle.USER_SYSTEM);
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2856eb4..f449126 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1019,9 +1019,7 @@
                     && scanInstallPackages(requests, createdAppId, versionInfos)) {
                 List<ReconciledPackage> reconciledPackages =
                         reconcileInstallPackages(requests, versionInfos);
-                if (reconciledPackages != null
-                        && renameAndUpdatePaths(requests)
-                        && commitInstallPackages(reconciledPackages)) {
+                if (reconciledPackages != null && commitInstallPackages(reconciledPackages)) {
                     success = true;
                 }
             }
@@ -1031,49 +1029,24 @@
         }
     }
 
-    private boolean renameAndUpdatePaths(List<InstallRequest> requests) {
+    private boolean prepareInstallPackages(List<InstallRequest> requests) {
+        // TODO: will remove the locking after doRename is moved out of prepare
         try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
             for (InstallRequest request : requests) {
-                ParsedPackage parsedPackage = request.getParsedPackage();
-                final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
-                if (isApex) {
-                    continue;
-                }
                 try {
-                    doRenameLI(request, parsedPackage);
-                    setUpFsVerity(parsedPackage);
-                } catch (Installer.InstallerException | IOException | DigestException
-                         | NoSuchAlgorithmException | PrepareFailure e) {
-                    request.setError(PackageManagerException.INTERNAL_ERROR_VERITY_SETUP,
-                            "Failed to set up verity: " + e);
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+                    request.onPrepareStarted();
+                    preparePackageLI(request);
+                } catch (PrepareFailure prepareFailure) {
+                    request.setError(prepareFailure.error,
+                            prepareFailure.getMessage());
+                    request.setOriginPackage(prepareFailure.mConflictingPackage);
+                    request.setOriginPermission(prepareFailure.mConflictingPermission);
                     return false;
+                } finally {
+                    request.onPrepareFinished();
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
-
-                // update paths that are set before renaming
-                PackageSetting scannedPackageSetting = request.getScannedPackageSetting();
-                scannedPackageSetting.setPath(new File(parsedPackage.getPath()));
-                scannedPackageSetting.setLegacyNativeLibraryPath(
-                        parsedPackage.getNativeLibraryRootDir());
-            }
-            return true;
-        }
-    }
-
-    private boolean prepareInstallPackages(List<InstallRequest> requests) {
-        for (InstallRequest request : requests) {
-            try {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
-                request.onPrepareStarted();
-                preparePackage(request);
-            } catch (PrepareFailure prepareFailure) {
-                request.setError(prepareFailure.error,
-                        prepareFailure.getMessage());
-                request.setOriginPackage(prepareFailure.mConflictingPackage);
-                request.setOriginPermission(prepareFailure.mConflictingPermission);
-                return false;
-            } finally {
-                request.onPrepareFinished();
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
         }
         return true;
@@ -1258,7 +1231,8 @@
         return newProp != null && newProp.getBoolean();
     }
 
-    private void preparePackage(InstallRequest request) throws PrepareFailure {
+    @GuardedBy("mPm.mInstallLock")
+    private void preparePackageLI(InstallRequest request) throws PrepareFailure {
         final int[] allUsers =  mPm.mUserManager.getUserIds();
         final int installFlags = request.getInstallFlags();
         final boolean onExternal = request.getVolumeUuid() != null;
@@ -1765,7 +1739,18 @@
             }
         }
 
-        if (isApex) {
+        if (!isApex) {
+            doRenameLI(request, parsedPackage);
+
+            try {
+                setUpFsVerity(parsedPackage);
+            } catch (Installer.InstallerException | IOException | DigestException
+                    | NoSuchAlgorithmException e) {
+                throw PrepareFailure.ofInternalError(
+                        "Failed to set up verity: " + e,
+                        PackageManagerException.INTERNAL_ERROR_VERITY_SETUP);
+            }
+        } else {
             // Use the path returned by apexd
             parsedPackage.setPath(request.getApexInfo().modulePath);
             parsedPackage.setBaseApkPath(request.getApexInfo().modulePath);
@@ -2107,21 +2092,7 @@
 
         // Reflect the rename in scanned details
         try {
-            String afterCanonicalPath = afterCodeFile.getCanonicalPath();
-            String beforeCanonicalPath = beforeCodeFile.getCanonicalPath();
-            parsedPackage.setPath(afterCanonicalPath);
-
-            parsedPackage.setNativeLibraryDir(
-                    parsedPackage.getNativeLibraryDir()
-                            .replace(beforeCanonicalPath, afterCanonicalPath));
-            parsedPackage.setNativeLibraryRootDir(
-                    parsedPackage.getNativeLibraryRootDir()
-                            .replace(beforeCanonicalPath, afterCanonicalPath));
-            String secondaryNativeLibraryDir = parsedPackage.getSecondaryNativeLibraryDir();
-            if (secondaryNativeLibraryDir != null) {
-                parsedPackage.setSecondaryNativeLibraryDir(
-                        secondaryNativeLibraryDir.replace(beforeCanonicalPath, afterCanonicalPath));
-            }
+            parsedPackage.setPath(afterCodeFile.getCanonicalPath());
         } catch (IOException e) {
             Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
             throw new PrepareFailure(PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f532342..76ea0b9 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -283,10 +283,7 @@
             return START_CLASS_NOT_FOUND;
         }
 
-        String currentLauncherPackageName = getCurrentLauncherPackageName(getParentUserId(userId));
-        if ((currentLauncherPackageName == null || !TextUtils.equals(callerPackageName,
-                currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
-            // TODO(b/311619990): Remove dependency on SHELL_UID for testing
+        if (!isCallerQualifiedForUnarchival(callerPackageName, callingUid, userId)) {
             Slog.e(TAG, TextUtils.formatSimple(
                     "callerPackageName: %s does not qualify for unarchival of package: " + "%s!",
                     callerPackageName, packageName));
@@ -335,6 +332,37 @@
         return START_ABORTED;
     }
 
+    private boolean isCallerQualifiedForUnarchival(String callerPackageName, int callingUid,
+            int userId) {
+        // TODO(b/311619990): Remove dependency on SHELL_UID for testing
+        if (callingUid == Process.SHELL_UID) {
+            return true;
+        }
+        String currentLauncherPackageName = getCurrentLauncherPackageName(getParentUserId(userId));
+        if (currentLauncherPackageName != null && TextUtils.equals(
+                callerPackageName, currentLauncherPackageName)) {
+            return true;
+        }
+        Slog.w(TAG, TextUtils.formatSimple(
+                "Requester of unarchival: %s is not the default launcher package: %s.",
+                callerPackageName, currentLauncherPackageName));
+        // When the default launcher is not set, or when the current caller is not the default
+        // launcher, allow the caller to directly request unarchive if it is a launcher app
+        // that is a pre-installed system app.
+        final Computer snapshot = mPm.snapshotComputer();
+        final PackageStateInternal ps = snapshot.getPackageStateInternal(callerPackageName);
+        final boolean isSystem = ps != null && ps.isSystem();
+        return isSystem && isLauncherApp(snapshot, callerPackageName, userId);
+    }
+
+    private boolean isLauncherApp(Computer snapshot, String packageName, int userId) {
+        final Intent intent = snapshot.getHomeIntent();
+        intent.setPackage(packageName);
+        List<ResolveInfo> launcherActivities = snapshot.queryIntentActivitiesInternal(
+                intent, null /* resolvedType */, 0 /* flags */, userId);
+        return !launcherActivities.isEmpty();
+    }
+
     // Profiles share their UI and default apps, so we have to get the profile parent before
     // fetching the default launcher.
     private int getParentUserId(int userId) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 09d2a02..83cb72e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -35,7 +35,8 @@
 
 public interface StatusBarManagerInternal {
     void setNotificationDelegate(NotificationDelegate delegate);
-    void showScreenPinningRequest(int taskId);
+    /** Show a screen pinning request for a specific task. */
+    void showScreenPinningRequest(int taskId, int userId);
     void showAssistDisclosure();
 
     void preloadRecentApps();
@@ -136,7 +137,7 @@
      *
      * @param hidesStatusBar whether it is being hidden
      */
-    void setTopAppHidesStatusBar(boolean hidesStatusBar);
+    void setTopAppHidesStatusBar(int displayId, boolean hidesStatusBar);
 
     boolean showShutdownUi(boolean isReboot, String requestString);
 
@@ -149,17 +150,18 @@
 
     /**
      * Notify System UI that the system get into or exit immersive mode.
+     * @param displayId The changed display Id.
      * @param rootDisplayAreaId The changed display area Id.
      * @param isImmersiveMode {@code true} if the display area get into immersive mode.
      */
-    void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode);
+    void immersiveModeChanged(int displayId, int rootDisplayAreaId, boolean isImmersiveMode);
 
     /**
      * Show a rotation suggestion that a user may approve to rotate the screen.
      *
      * @param rotation rotation suggestion
      */
-    void onProposedRotationChanged(int rotation, boolean isValid);
+    void onProposedRotationChanged(int displayId, int rotation, boolean isValid);
 
     /**
      * Notifies System UI that the display is ready to show system decorations.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0fd5967..908f51b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -119,6 +119,7 @@
 import com.android.server.UiThread;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.notification.NotificationDelegate;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.GlobalActionsProvider;
 import com.android.server.power.ShutdownCheckPoints;
@@ -185,6 +186,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final ActivityTaskManagerInternal mActivityTaskManager;
     private final PackageManagerInternal mPackageManagerInternal;
+    private final UserManagerInternal mUserManagerInternal;
     private final SessionMonitor mSessionMonitor;
     private int mCurrentUserId;
     private boolean mTracingEnabled;
@@ -304,6 +306,7 @@
         mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
         mTileRequestTracker = new TileRequestTracker(mContext);
         mSessionMonitor = new SessionMonitor(mContext);
@@ -360,7 +363,14 @@
         }
 
         @Override
-        public void showScreenPinningRequest(int taskId) {
+        public void showScreenPinningRequest(int taskId, int userId) {
+            if (isVisibleBackgroundUser(userId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping showScreenPinningRequest for visible background user "
+                            + userId);
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -439,6 +449,13 @@
 
         @Override
         public void appTransitionFinished(int displayId) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping appTransitionFinished for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             enforceStatusBarService();
             IStatusBar bar = mBar;
             if (bar != null) {
@@ -588,6 +605,13 @@
 
         @Override
         public void setWindowState(int displayId, int window, int state) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping setWindowState for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -598,6 +622,13 @@
 
         @Override
         public void appTransitionPending(int displayId) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping appTransitionPending for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -608,6 +639,13 @@
 
         @Override
         public void appTransitionCancelled(int displayId) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping appTransitionCancelled for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -619,6 +657,13 @@
         @Override
         public void appTransitionStarting(int displayId, long statusBarAnimationsStartTime,
                 long statusBarAnimationsDuration) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping appTransitionStarting for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -629,7 +674,14 @@
         }
 
         @Override
-        public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
+        public void setTopAppHidesStatusBar(int displayId, boolean hidesStatusBar) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping setTopAppHidesStatusBar for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -665,10 +717,18 @@
         }
 
         @Override
-        public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+        public void immersiveModeChanged(int displayId, int rootDisplayAreaId,
+                boolean isImmersiveMode) {
             if (mBar == null) {
                 return;
             }
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping immersiveModeChanged for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             if (!CLIENT_TRANSIENT) {
                 // Only call from here when the client transient is not enabled.
                 try {
@@ -680,7 +740,14 @@
 
         // TODO(b/118592525): support it per display if necessary.
         @Override
-        public void onProposedRotationChanged(int rotation, boolean isValid) {
+        public void onProposedRotationChanged(int displayId, int rotation, boolean isValid) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping onProposedRotationChanged for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             if (mBar != null){
                 try {
                     mBar.onProposedRotationChanged(rotation, isValid);
@@ -690,6 +757,13 @@
 
         @Override
         public void onDisplayReady(int displayId) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping onDisplayReady for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -703,6 +777,13 @@
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
                 @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping onSystemBarAttributesChanged for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
                     navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
@@ -719,6 +800,13 @@
         @Override
         public void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping showTransient for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             getUiState(displayId).showTransient(types);
             IStatusBar bar = mBar;
             if (bar != null) {
@@ -730,6 +818,13 @@
 
         @Override
         public void abortTransient(int displayId, @InsetsType int types) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG, "Skipping abortTransient for visible background user "
+                            + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             getUiState(displayId).clearTransient(types);
             IStatusBar bar = mBar;
             if (bar != null) {
@@ -776,6 +871,15 @@
 
         @Override
         public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+            if (isVisibleBackgroundUserOnDisplay(displayId)) {
+                if (SPEW) {
+                    Slog.d(TAG,
+                            "Skipping setNavigationBarLumaSamplingEnabled for visible background "
+                                    + "user "
+                                    + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+                }
+                return;
+            }
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
@@ -1416,6 +1520,13 @@
     }
 
     private void setDisableFlags(int displayId, int flags, String cause) {
+        if (isVisibleBackgroundUserOnDisplay(displayId)) {
+            if (SPEW) {
+                Slog.d(TAG, "Skipping setDisableFlags for visible background user "
+                        + mUserManagerInternal.getUserAssignedToDisplay(displayId));
+            }
+            return;
+        }
         // also allows calls from window manager which is in this process.
         enforceStatusBarService();
 
@@ -2713,16 +2824,30 @@
         if (callingUserId == USER_SYSTEM || callingUserId == mCurrentUserId) {
             return;
         }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            if (mUserManager.isSameProfileGroup(callingUserId, mCurrentUserId)) {
-                return;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+        if (!isVisibleBackgroundUser(callingUserId)) {
+            return;
         }
 
         throw new SecurityException("User " + callingUserId
                 + " is not permitted to use this method");
     }
-}
+
+    private boolean isVisibleBackgroundUser(int userId) {
+        if (!mVisibleBackgroundUsersEnabled) {
+            return false;
+        }
+        // The main use case for visible background users is the Automotive multi-display
+        // configuration where a passenger can use a secondary display while the driver is
+        // using the main display.
+        // TODO(b/341604160) - Support visible background users properly and remove carve outs
+        return mUserManagerInternal.isVisibleBackgroundFullUser(userId);
+    }
+
+    private boolean isVisibleBackgroundUserOnDisplay(int displayId) {
+        if (!mVisibleBackgroundUsersEnabled) {
+            return false;
+        }
+        int userId = mUserManagerInternal.getUserAssignedToDisplay(displayId);
+        return isVisibleBackgroundUser(userId);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 47425322..5f704a0 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -276,7 +276,7 @@
         // enabled on the last one as a sample
         mInboundTransform = inboundTransform;
 
-        if (!Flags.allowDisableIpsecLossDetector() || canStart()) {
+        if (canStart()) {
             start();
         }
     }
@@ -292,7 +292,7 @@
             mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
         }
 
-        if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) {
+        if (canStart() != isStarted()) {
             if (canStart()) {
                 start();
             } else {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4dcc6e1..78359bd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2224,15 +2224,21 @@
     public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
             IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
             boolean getCropped) {
-        final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL)
-                || hasPermission(MANAGE_EXTERNAL_STORAGE);
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
         if (!hasPrivilege) {
-            mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
-                    Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
+            boolean hasManageExternalStorage = hasPermission(MANAGE_EXTERNAL_STORAGE)
+                    || hasAppOpPermission(MANAGE_EXTERNAL_STORAGE, callingUid, callingPkg,
+                        callingFeatureId, "getWallpaperWithFeature from package: " + callingPkg);
+            if (!hasManageExternalStorage) {
+                mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
+                        callingPid, callingUid, callingPkg, callingFeatureId);
+            }
         }
 
-        wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
+        wallpaperUserId = ActivityManager.handleIncomingUser(callingPid, callingUid,
+                wallpaperUserId, false, true, "getWallpaper", null);
 
         if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
             throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to read");
@@ -2348,6 +2354,22 @@
         return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
     }
 
+    private boolean hasAppOpPermission(String permission, int callingUid, String callingPackage,
+            String attributionTag, String message) {
+        final String op = AppOpsManager.permissionToOp(permission);
+        final int opMode = mAppOpsManager.noteOpNoThrow(op, callingUid, callingPackage,
+                attributionTag, message);
+        switch (opMode) {
+            case AppOpsManager.MODE_ALLOWED:
+            case AppOpsManager.MODE_FOREGROUND:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+                return hasPermission(permission);
+            default:
+                return false;
+        }
+    }
+
     @Override
     public WallpaperInfo getWallpaperInfo(int userId) {
         return getWallpaperInfoWithFlags(FLAG_SYSTEM, userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0be6471..ff6f021 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2848,7 +2848,7 @@
         final boolean hasImeSurface;
         if (mStartingData != null) {
             if (mStartingData.mWaitForSyncTransactionCommit
-                    || mTransitionController.isCollecting(this)) {
+                    || mSyncState != SYNC_STATE_NONE) {
                 mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
                 return;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4e4616d..0646fb7 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1072,8 +1072,10 @@
             return close.asWindowState() != null;
         }
 
-        private void initiate(@NonNull WindowContainer close, @NonNull WindowContainer[] open,
+        private void initiate(ScheduleAnimationBuilder builder,
                 @NonNull ActivityRecord[] openingActivities)  {
+            WindowContainer close = builder.mCloseTarget;
+            WindowContainer[] open = builder.mOpenTargets;
             if (isActivitySwitch(close, open)) {
                 mSwitchType = ACTIVITY_SWITCH;
                 if (Flags.migratePredictiveBackTransition()) {
@@ -1091,9 +1093,17 @@
                 return;
             }
 
-            mCloseAdaptor = createAdaptor(close, false, mSwitchType);
+            final Transition prepareTransition = builder.prepareTransitionIfNeeded(
+                    openingActivities);
+            final SurfaceControl.Transaction st = openingActivities[0].getSyncTransaction();
+            final SurfaceControl.Transaction ct = prepareTransition != null
+                    ? st : close.getPendingTransaction();
+            mCloseAdaptor = createAdaptor(close, false, mSwitchType, ct);
             if (mCloseAdaptor.mAnimationTarget == null) {
                 Slog.w(TAG, "composeNewAnimations fail, skip");
+                if (prepareTransition != null) {
+                    prepareTransition.abort();
+                }
                 clearBackAnimateTarget(true /* cancel */);
                 return;
             }
@@ -1110,12 +1120,17 @@
                             next.getWindowConfiguration().getRotation());
                 }
             }
-            mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
+            mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(
+                    true, mSwitchType, st, open);
             if (!mOpenAnimAdaptor.isValid()) {
                 Slog.w(TAG, "compose animations fail, skip");
+                if (prepareTransition != null) {
+                    prepareTransition.abort();
+                }
                 clearBackAnimateTarget(true /* cancel */);
                 return;
             }
+            mOpenAnimAdaptor.mPreparedOpenTransition = prepareTransition;
             mOpenActivities = openingActivities;
         }
 
@@ -1147,19 +1162,21 @@
             return new Pair<>(replaceClose, replaceOpen);
         }
 
-        private boolean composeAnimations(@NonNull WindowContainer close,
-                @NonNull WindowContainer[] open, @NonNull ActivityRecord[] openingActivities) {
+        private boolean composeAnimations(@NonNull ScheduleAnimationBuilder builder,
+                @NonNull ActivityRecord[] openingActivities) {
             if (mComposed || mWaitTransition) {
                 Slog.e(TAG, "Previous animation is running " + this);
                 return false;
             }
             clearBackAnimateTarget(true /* cancel */);
-            if (close == null || open == null || open.length == 0 || open.length > 2) {
+            final WindowContainer[] open = builder.mOpenTargets;
+            if (builder.mCloseTarget == null || open == null || open.length == 0
+                    || open.length > 2) {
                 Slog.e(TAG, "reset animation with null target close: "
-                        + close + " open: " + Arrays.toString(open));
+                        + builder.mCloseTarget + " open: " + Arrays.toString(open));
                 return false;
             }
-            initiate(close, open, openingActivities);
+            initiate(builder, openingActivities);
             if (mSwitchType == UNKNOWN) {
                 return false;
             }
@@ -1384,10 +1401,10 @@
         }
 
         @NonNull private static BackWindowAnimationAdaptor createAdaptor(
-                @NonNull WindowContainer target, boolean isOpen, int switchType) {
+                @NonNull WindowContainer target, boolean isOpen, int switchType,
+                SurfaceControl.Transaction st) {
             final BackWindowAnimationAdaptor adaptor =
                     new BackWindowAnimationAdaptor(target, isOpen, switchType);
-            final SurfaceControl.Transaction pt = target.getPendingTransaction();
             // Workaround to show TaskFragment which can be hide in Transitions and won't show
             // during isAnimating.
             if (isOpen && target.asActivityRecord() != null) {
@@ -1395,10 +1412,10 @@
                 if (fragment != null) {
                     // Ensure task fragment surface has updated, in case configuration has changed.
                     fragment.updateOrganizedTaskFragmentSurface();
-                    pt.show(fragment.mSurfaceControl);
+                    st.show(fragment.mSurfaceControl);
                 }
             }
-            target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
+            target.startAnimation(st, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
             return adaptor;
         }
 
@@ -1417,12 +1434,12 @@
             private Transition mPreparedOpenTransition;
 
             BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
-                    @NonNull WindowContainer... targets) {
+                    SurfaceControl.Transaction st, @NonNull WindowContainer... targets) {
                 mAdaptors = new BackWindowAnimationAdaptor[targets.length];
                 for (int i = targets.length - 1; i >= 0; --i) {
-                    mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType);
+                    mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType, st);
                 }
-                mRemoteAnimationTarget = targets.length > 1 ? createWrapTarget()
+                mRemoteAnimationTarget = targets.length > 1 ? createWrapTarget(st)
                         : mAdaptors[0].mAnimationTarget;
             }
 
@@ -1448,7 +1465,7 @@
                 mPreparedOpenTransition = null;
             }
 
-            private RemoteAnimationTarget createWrapTarget() {
+            private RemoteAnimationTarget createWrapTarget(SurfaceControl.Transaction st) {
                 // Special handle for opening two activities together.
                 // If we animate both activities separately, the animation area and rounded corner
                 // would also being handled separately. To make them seem like "open" together, wrap
@@ -1470,12 +1487,11 @@
                         .build();
                 mCloseTransaction = new SurfaceControl.Transaction();
                 mCloseTransaction.reparent(leashSurface, null);
-                final SurfaceControl.Transaction pt = wc.getPendingTransaction();
-                pt.setLayer(leashSurface, wc.getLastLayer());
+                st.setLayer(leashSurface, wc.getLastLayer());
                 for (int i = mAdaptors.length - 1; i >= 0; --i) {
                     BackWindowAnimationAdaptor adaptor = mAdaptors[i];
-                    pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
-                    pt.setPosition(adaptor.mAnimationTarget.leash,
+                    st.reparent(adaptor.mAnimationTarget.leash, leashSurface);
+                    st.setPosition(adaptor.mAnimationTarget.leash,
                             adaptor.mAnimationTarget.localBounds.left,
                             adaptor.mAnimationTarget.localBounds.top);
                     // For adjacent activity embedded, reparent Activity to TaskFragment when
@@ -1738,6 +1754,7 @@
             WindowContainer mCloseTarget;
             WindowContainer[] mOpenTargets;
             boolean mIsLaunchBehind;
+            TaskSnapshot mSnapshot;
 
             ScheduleAnimationBuilder(int type, BackAnimationAdapter adapter,
                     NavigationMonitor monitor) {
@@ -1771,6 +1788,13 @@
                 return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
             }
 
+            private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities) {
+                if (mSnapshot == null) {
+                    return setLaunchBehind(visibleOpenActivities);
+                }
+                return null;
+            }
+
             /**
              * Apply preview strategy on the opening target
              *
@@ -1780,26 +1804,17 @@
             private void applyPreviewStrategy(
                     @NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor,
                     @NonNull ActivityRecord[] visibleOpenActivities) {
-                boolean needsLaunchBehind = true;
                 if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
                     boolean activitiesAreDrawn = false;
                     for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
                         // If the activity hasn't stopped, it's window should remain drawn.
                         activitiesAreDrawn |= visibleOpenActivities[i].firstWindowDrawn;
                     }
-                    final WindowContainer mainOpen = openAnimationAdaptor.mAdaptors[0].mTarget;
-                    final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
                     // Don't create starting surface if previous activities haven't stopped or
                     // the snapshot does not exist.
-                    if (snapshot != null || !activitiesAreDrawn) {
-                        openAnimationAdaptor.createStartingSurface(snapshot);
+                    if (mSnapshot != null || !activitiesAreDrawn) {
+                        openAnimationAdaptor.createStartingSurface(mSnapshot);
                     }
-                    // Only use LaunchBehind if snapshot does not exist.
-                    needsLaunchBehind = snapshot == null;
-                }
-                if (needsLaunchBehind) {
-                    openAnimationAdaptor.mPreparedOpenTransition =
-                            setLaunchBehind(visibleOpenActivities);
                 }
                 // Force update mLastSurfaceShowing for opening activity and its task.
                 if (mWindowManagerService.mRoot.mTransitionController.isShellTransitionsEnabled()) {
@@ -1821,7 +1836,11 @@
                     return null;
                 }
 
-                if (!composeAnimations(mCloseTarget, mOpenTargets, openingActivities)) {
+                if (!shouldLaunchBehind && mShowWindowlessSurface) {
+                    mSnapshot = getSnapshot(mOpenTargets[0], openingActivities);
+                }
+
+                if (!composeAnimations(this, openingActivities)) {
                     return null;
                 }
                 mCloseTarget.mTransitionController.mSnapshotController
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 107d31e..0fa1a21 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2517,7 +2517,7 @@
         if (getStatusBar() != null) {
             final StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
             if (statusBar != null) {
-                statusBar.setTopAppHidesStatusBar(topAppHidesStatusBar);
+                statusBar.setTopAppHidesStatusBar(getDisplayId(), topAppHidesStatusBar);
             }
         }
 
@@ -2544,9 +2544,9 @@
                         mService.mPolicy.isUserSetupComplete(),
                         isNavBarEmpty(disableFlags));
             } else {
-                // TODO (b/277290737): Move this to the client side, instead of using a proxy.
-                callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(rootDisplayAreaId,
-                        isImmersiveMode));
+                // TODO(b/277290737): Move this to the client side, instead of using a proxy.
+                callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(getDisplayId(),
+                        rootDisplayAreaId, isImmersiveMode));
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 5200e82..8c06cfe 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -81,7 +81,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
-import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -136,7 +135,6 @@
     private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
 
     private OrientationListener mOrientationListener;
-    private StatusBarManagerInternal mStatusBarManagerInternal;
     private SettingsObserver mSettingsObserver;
     @NonNull
     private final DeviceStateController mDeviceStateController;
@@ -1559,11 +1557,9 @@
 
     /** Notify the StatusBar that system rotation suggestion has changed. */
     private void sendProposedRotationChangeToStatusBarInternal(int rotation, boolean isValid) {
-        if (mStatusBarManagerInternal == null) {
-            mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
-        }
-        if (mStatusBarManagerInternal != null) {
-            mStatusBarManagerInternal.onProposedRotationChanged(rotation, isValid);
+        final StatusBarManagerInternal bar = mDisplayPolicy.getStatusBarManagerInternal();
+        if (bar != null) {
+            bar.onProposedRotationChanged(mDisplayContent.getDisplayId(), rotation, isValid);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 0dadade..e65396e 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -659,7 +659,7 @@
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
                         StatusBarManagerInternal.class);
                 if (statusBarManager != null) {
-                    statusBarManager.showScreenPinningRequest(task.mTaskId);
+                    statusBarManager.showScreenPinningRequest(task.mTaskId, task.mUserId);
                 }
                 return;
             } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e6226ab..1e3de12 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -73,6 +73,7 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -223,6 +224,13 @@
     private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>();
 
     /**
+     * Tracks the top display like top tasks so we can trigger a MOVED_TO_TOP transition even when
+     * a display gets moved to front but there's no change in per-display focused tasks.
+     */
+    private DisplayContent mOnTopDisplayStart = null;
+    private DisplayContent mOnTopDisplayAtReady = null;
+
+    /**
      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
      * the transition animation.
      */
@@ -772,6 +780,10 @@
         if (dc == null || mTargetDisplays.contains(dc)) return;
         mTargetDisplays.add(dc);
         addOnTopTasks(dc, mOnTopTasksStart);
+        if (mOnTopDisplayStart == null) {
+            mOnTopDisplayStart =
+                    mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
+        }
         // Handle the case {transition.start(); applyTransaction(wct);} that the animating state
         // is set before collecting participants.
         if (mController.isAnimating()) {
@@ -995,6 +1007,8 @@
             for (int i = 0; i < mTargetDisplays.size(); ++i) {
                 addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
             }
+            mOnTopDisplayAtReady =
+                    mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
             mController.onTransitionPopulated(this);
         }
     }
@@ -2079,6 +2093,10 @@
                 return true;
             }
         }
+        if (enableDisplayFocusInShellTransitions() && mOnTopDisplayStart
+                != mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent()) {
+            return true;
+        }
         return false;
     }
 
@@ -2110,6 +2128,8 @@
             includesOrderChange = true;
             break;
         }
+        includesOrderChange |= enableDisplayFocusInShellTransitions()
+                && mOnTopDisplayStart != mOnTopDisplayAtReady;
         if (!includesOrderChange && !reportCurrent) {
             // This transition doesn't include an order change, so if it isn't required to report
             // the current focus (eg. it's the last of a cluster of transitions), then don't
@@ -2120,6 +2140,8 @@
         // latest state and compare with the last reported state (or our start state if no
         // reported state exists).
         ArrayList<Task> onTopTasksEnd = new ArrayList<>();
+        final DisplayContent onTopDisplayEnd =
+                mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
         for (int d = 0; d < mTargetDisplays.size(); ++d) {
             addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
             final int displayId = mTargetDisplays.get(d).mDisplayId;
@@ -2127,11 +2149,15 @@
             for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
                 final Task task = onTopTasksEnd.get(i);
                 if (task.getDisplayId() != displayId) continue;
-                // If it didn't change since last report, don't report
-                if (reportedOnTop == null) {
-                    if (mOnTopTasksStart.contains(task)) continue;
-                } else if (reportedOnTop.contains(task)) {
-                    continue;
+                if (!enableDisplayFocusInShellTransitions()
+                        || mOnTopDisplayStart == onTopDisplayEnd
+                        || displayId != onTopDisplayEnd.mDisplayId) {
+                    // If it didn't change since last report, don't report
+                    if (reportedOnTop == null) {
+                        if (mOnTopTasksStart.contains(task)) continue;
+                    } else if (reportedOnTop.contains(task)) {
+                        continue;
+                    }
                 }
                 // Need to report it.
                 mParticipants.add(task);
@@ -2333,10 +2359,7 @@
             // Place the nav bar on top of anything else in the top activity.
             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
         }
-        final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
-        if (bar != null) {
-            bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
-        }
+        sendLumaSamplingEnabledToStatusBarInternal(dc, false);
     }
 
     /** @see RecentsAnimationController#restoreNavigationBarFromApp */
@@ -2354,10 +2377,7 @@
 
         final DisplayContent dc =
                 mController.mAtm.mRootWindowContainer.getDisplayContent(recentsDisplayId);
-        final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
-        if (bar != null) {
-            bar.setNavigationBarLumaSamplingEnabled(recentsDisplayId, true);
-        }
+        sendLumaSamplingEnabledToStatusBarInternal(dc, true);
         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
         if (navWindow == null) return;
         navWindow.setSurfaceTranslationY(0);
@@ -2391,6 +2411,14 @@
         dc.mWmService.scheduleAnimationLocked();
     }
 
+    private void sendLumaSamplingEnabledToStatusBarInternal(@NonNull DisplayContent dc,
+            boolean enabled) {
+        final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
+        if (bar != null) {
+            bar.setNavigationBarLumaSamplingEnabled(dc.getDisplayId(), enabled);
+        }
+    }
+
     private void reportStartReasonsToLogger() {
         // Record transition start in metrics logger. We just assume everything is "DRAWN"
         // at this point since splash-screen is a presentation (shell) detail.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fb57a1b..d63cdcd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -90,6 +90,7 @@
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -158,6 +159,7 @@
 import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
 import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 
 import android.Manifest;
 import android.Manifest.permission;
@@ -3238,9 +3240,28 @@
                     return;
                 }
 
+                Transition transition = null;
+                boolean transitionNewlyCreated = false;
+                if (enableDisplayFocusInShellTransitions()) {
+                    transition = mAtmService.getTransitionController().requestTransitionIfNeeded(
+                                    TRANSIT_TO_FRONT, 0 /* flags */, null /* trigger */,
+                                    displayContent);
+                    if (transition != null) {
+                        transitionNewlyCreated = true;
+                    } else {
+                        transition =
+                                mAtmService.getTransitionController().getCollectingTransition();
+                    }
+                    if (transition != null) {
+                        transition.recordTaskOrder(displayContent);
+                    }
+                }
                 // Nothing prevented us from moving the display to the top. Let's do it!
                 displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
                         displayContent, true /* includingParents */);
+                if (transitionNewlyCreated) {
+                    transition.setReady(displayContent, true /* ready */);
+                }
             }
         }
     }
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
new file mode 100644
index 0000000..650e520
--- /dev/null
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions
+
+import android.app.appsearch.AppSearchSchema
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AppFunctionRuntimeMetadataTest {
+
+    @Test
+    fun getRuntimeSchemaNameForPackage() {
+        val actualSchemaName =
+            AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage("com.example.app")
+
+        assertThat(actualSchemaName).isEqualTo("AppFunctionRuntimeMetadata-com.example.app")
+    }
+
+    @Test
+    fun testCreateChildRuntimeSchema() {
+        val runtimeSchema: AppSearchSchema =
+            AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema("com.example.app")
+
+        assertThat(runtimeSchema.schemaType).isEqualTo("AppFunctionRuntimeMetadata-com.example.app")
+        val propertyNameSet = runtimeSchema.properties.map { it.name }.toSet()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
+            .isTrue()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME))
+            .isTrue()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_ENABLED)).isTrue()
+        assertThat(
+                propertyNameSet.contains(
+                    AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID
+                )
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun testCreateParentRuntimeSchema() {
+        val runtimeSchema: AppSearchSchema =
+            AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema()
+
+        assertThat(runtimeSchema.schemaType).isEqualTo("AppFunctionRuntimeMetadata")
+        val propertyNameSet = runtimeSchema.properties.map { it.name }.toSet()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
+            .isTrue()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME))
+            .isTrue()
+        assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_ENABLED)).isTrue()
+        assertThat(
+                propertyNameSet.contains(
+                    AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID
+                )
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun testGetPackageNameFromSchema() {
+        val expectedPackageName = "com.foo.test"
+        val expectedPackageName2 = "com.bar.test"
+        val testSchemaType =
+            AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(expectedPackageName)
+                .schemaType
+        val testSchemaType2 =
+            AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(expectedPackageName2)
+                .schemaType
+
+        val actualPackageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType)
+        val actualPackageName2 =
+            AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType2)
+
+        assertThat(actualPackageName).isEqualTo(expectedPackageName)
+        assertThat(actualPackageName2).isEqualTo(expectedPackageName2)
+    }
+
+    @Test
+    fun testGetPackageNameFromParentSchema() {
+        val expectedPackageName = AppFunctionRuntimeMetadata.APP_FUNCTION_INDEXER_PACKAGE
+        val testSchemaType =
+            AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema().schemaType
+
+        val actualPackageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType)
+
+        assertThat(actualPackageName).isEqualTo(expectedPackageName)
+    }
+}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
index a0f1a55..3bc4411 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
@@ -19,8 +19,10 @@
 import android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE
 import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema
 import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema
+import android.app.appsearch.AppSearchBatchResult
 import android.app.appsearch.AppSearchManager
 import android.app.appsearch.PutDocumentsRequest
+import android.app.appsearch.RemoveByDocumentIdRequest
 import android.app.appsearch.SearchSpec
 import android.app.appsearch.SetSchemaRequest
 import androidx.test.platform.app.InstrumentationRegistry
@@ -56,7 +58,7 @@
                 SetSchemaRequest.Builder()
                     .addSchemas(
                         createParentAppFunctionRuntimeSchema(),
-                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME),
                     )
                     .build()
 
@@ -74,7 +76,7 @@
                 SetSchemaRequest.Builder()
                     .addSchemas(
                         createParentAppFunctionRuntimeSchema(),
-                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME),
                     )
                     .build()
             val schema = session.setSchema(setSchemaRequest)
@@ -93,6 +95,40 @@
     }
 
     @Test
+    fun remove() {
+        val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+        FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
+            val setSchemaRequest =
+                SetSchemaRequest.Builder()
+                    .addSchemas(
+                        createParentAppFunctionRuntimeSchema(),
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME),
+                    )
+                    .build()
+            val schema = session.setSchema(setSchemaRequest)
+            assertThat(schema.get()).isNotNull()
+            val appFunctionRuntimeMetadata =
+                AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+            val putDocumentsRequest: PutDocumentsRequest =
+                PutDocumentsRequest.Builder()
+                    .addGenericDocuments(appFunctionRuntimeMetadata)
+                    .build()
+            val putResult = session.put(putDocumentsRequest)
+            assertThat(putResult.get().isSuccess).isTrue()
+            val removeDocumentRequest =
+                RemoveByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
+                    .addIds(appFunctionRuntimeMetadata.id)
+                    .build()
+
+            val removeResult: AppSearchBatchResult<String, Void> =
+                session.remove(removeDocumentRequest).get()
+
+            assertThat(removeResult).isNotNull()
+            assertThat(removeResult.isSuccess).isTrue()
+        }
+    }
+
+    @Test
     fun search() {
         val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
         FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
@@ -100,7 +136,7 @@
                 SetSchemaRequest.Builder()
                     .addSchemas(
                         createParentAppFunctionRuntimeSchema(),
-                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME),
                     )
                     .build()
             val schema = session.setSchema(setSchemaRequest)
@@ -132,7 +168,7 @@
                 SetSchemaRequest.Builder()
                     .addSchemas(
                         createParentAppFunctionRuntimeSchema(),
-                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+                        createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME),
                     )
                     .build()
             val schema = session.setSchema(setSchemaRequest)
@@ -144,12 +180,13 @@
                     .build()
             val putResult = session.put(putDocumentsRequest)
 
-            val genricDocument = session
-                .getByDocumentId(
-                    /* documentId= */ "${TEST_PACKAGE_NAME}/${TEST_FUNCTION_ID}",
-                    APP_FUNCTION_RUNTIME_NAMESPACE
-                )
-                .get()
+            val genricDocument =
+                session
+                    .getByDocumentId(
+                        /* documentId= */ "${TEST_PACKAGE_NAME}/${TEST_FUNCTION_ID}",
+                        APP_FUNCTION_RUNTIME_NAMESPACE,
+                    )
+                    .get()
 
             val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genricDocument)
             assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID)
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 1061da2..5769e07 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -16,12 +16,9 @@
 package com.android.server.appfunctions
 
 import android.app.appfunctions.AppFunctionRuntimeMetadata
-import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID
-import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME
 import android.app.appsearch.AppSearchManager
 import android.app.appsearch.AppSearchManager.SearchContext
 import android.app.appsearch.PutDocumentsRequest
-import android.app.appsearch.SearchSpec
 import android.app.appsearch.SetSchemaRequest
 import android.util.ArrayMap
 import android.util.ArraySet
@@ -76,20 +73,11 @@
                 testExecutor,
                 FutureAppSearchSession(appSearchManager, testExecutor, searchContext),
             )
-        val searchSpec: SearchSpec =
-            SearchSpec.Builder()
-                .addFilterSchemas(
-                    AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
-                    AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
-                        .schemaType,
-                )
-                .build()
         val packageToFunctionIdMap =
             metadataSyncAdapter.getPackageToFunctionIdMap(
-                "",
-                searchSpec,
-                PROPERTY_FUNCTION_ID,
-                PROPERTY_PACKAGE_NAME,
+                AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
+                AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+                AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME,
             )
 
         assertThat(packageToFunctionIdMap).isNotNull()
@@ -135,21 +123,11 @@
                 testExecutor,
                 FutureAppSearchSession(appSearchManager, testExecutor, searchContext),
             )
-        val searchSpec: SearchSpec =
-            SearchSpec.Builder()
-                .setResultCountPerPage(1)
-                .addFilterSchemas(
-                    AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
-                    AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
-                        .schemaType,
-                )
-                .build()
         val packageToFunctionIdMap =
             metadataSyncAdapter.getPackageToFunctionIdMap(
-                "",
-                searchSpec,
-                PROPERTY_FUNCTION_ID,
-                PROPERTY_PACKAGE_NAME,
+                AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
+                AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+                AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME,
             )
 
         assertThat(packageToFunctionIdMap).isNotNull()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 52f1cbd..76c8e686 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -143,6 +143,7 @@
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.layout.Layout;
 import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
@@ -3201,6 +3202,45 @@
                 argThat(matchesFilter));
     }
 
+    @Test
+    public void testOnDisplayChanged_HbmMetadataNull() {
+        DisplayPowerController dpc = mock(DisplayPowerController.class);
+        DisplayManagerService.Injector injector = new BasicInjector() {
+            @Override
+            DisplayPowerController getDisplayPowerController(Context context,
+                    DisplayPowerController.Injector injector,
+                    DisplayManagerInternal.DisplayPowerCallbacks callbacks, Handler handler,
+                    SensorManager sensorManager, DisplayBlanker blanker,
+                    LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker,
+                    BrightnessSetting brightnessSetting, Runnable onBrightnessChangeRunnable,
+                    HighBrightnessModeMetadata hbmMetadata, boolean bootCompleted,
+                    DisplayManagerFlags flags) {
+                return dpc;
+            }
+        };
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, injector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        // Add the FakeDisplayDevice
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.state = Display.STATE_ON;
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        initDisplayPowerController(localService);
+
+        // Simulate DisplayDevice change
+        DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
+        displayDeviceInfo2.copyFrom(displayDeviceInfo);
+        displayDeviceInfo2.state = Display.STATE_DOZE;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo2);
+
+        verify(dpc).onDisplayChanged(/* hbmMetadata= */ null, Layout.NO_LEAD_DISPLAY);
+    }
+
     private void initDisplayPowerController(DisplayManagerInternal localService) {
         localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
             @Override
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 5a76931..01435ff 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -138,8 +138,6 @@
     auto_gen_config: true,
 }
 
-FLAKY = ["androidx.test.filters.FlakyTest"]
-
 test_module_config {
     name: "FrameworksMockingServicesTests_blob",
     base: "FrameworksMockingServicesTests",
@@ -152,7 +150,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.DeviceIdleControllerTest"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -161,7 +158,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.AppStateTrackerTest"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -177,7 +173,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.alarm"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -185,7 +180,7 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.job"],
-    exclude_annotations: FLAKY + ["androidx.test.filters.LargeTest"],
+    exclude_annotations: ["androidx.test.filters.LargeTest"],
 }
 
 test_module_config {
@@ -200,7 +195,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.tare"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -215,7 +209,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["android.service.games"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -245,7 +238,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.am."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -265,7 +257,6 @@
     test_suites: ["device-tests"],
     // Matches appop too
     include_filters: ["com.android.server.app"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -301,7 +292,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.pm"],
-    exclude_annotations: FLAKY + ["org.junit.Ignore"],
 }
 
 test_module_config {
@@ -309,7 +299,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.power"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -324,7 +313,6 @@
     base: "FrameworksMockingServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.trust"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index d957355..5ec5302 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -438,6 +438,28 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_TopSleepingReceivingBroadcast() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP_SLEEPING).when(mService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(mService).getTopApp();
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, FOREGROUND_APP_ADJ,
+                SCHED_GROUP_BACKGROUND);
+        assertTrue(app.mState.hasForegroundActivities());
+
+        doReturn(true).when(mService).isReceivingBroadcastLocked(any(ProcessRecord.class),
+                any(int[].class));
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_RECEIVER, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
+        assertTrue(app.mState.hasForegroundActivities());
+
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_ExecutingService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
@@ -1182,6 +1204,25 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_BoundFgService_Sleeping() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
+        client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+        updateOomAdj(client, app);
+        mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+        assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
+                SCHED_GROUP_RESTRICTED);
+        assertProcStates(client, PROCESS_STATE_PERSISTENT, PERSISTENT_PROC_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_Service_BoundNotForeground() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -2766,6 +2807,30 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_AboveClient() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(mService).getTopApp();
+        mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdj(app);
+
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+        // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
+        // verify that its OOM adjustment level is unaffected.
+        bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+        app.mServices.updateHasAboveClientLocked();
+        assertTrue(app.mServices.hasAboveClient());
+
+        updateOomAdj(app);
+        assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index a862c43..914b29f 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -36,6 +36,7 @@
         "-Werror",
     ],
     static_libs: [
+        "a11ychecker",
         "aatf",
         "accessibility_protos_lite",
         "cts-input-lib",
@@ -273,21 +274,12 @@
         "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
 }
 
-FLAKY = [
-    "androidx.test.filters.FlakyTest",
-]
-
-FLAKY_AND_IGNORED = [
-    "androidx.test.filters.FlakyTest",
-    "org.junit.Ignore",
-]
 // Used by content protection TEST_MAPPING
 test_module_config {
     name: "FrameworksServicesTests_contentprotection",
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.contentprotection"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -295,7 +287,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.om."],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 // Used by contexthub TEST_MAPPING
@@ -306,7 +297,6 @@
     include_filters: ["com.android.server.location.contexthub."],
     // TODO(ron): are these right, does it run anything?
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -316,7 +306,6 @@
     include_filters: ["com.android.server.location.contexthub."],
     // TODO(ron): are these right, does it run anything?
     include_annotations: ["android.platform.test.annotations.Postsubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 // Used by contentcapture
@@ -325,7 +314,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.contentcapture"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -333,7 +321,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.recoverysystem."],
-    exclude_annotations: FLAKY,
 }
 
 // server pm TEST_MAPPING
@@ -343,7 +330,6 @@
     test_suites: ["device-tests"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
     include_filters: ["com.android.server.pm."],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -352,7 +338,6 @@
     test_suites: ["device-tests"],
     include_annotations: ["android.platform.test.annotations.Postsubmit"],
     include_filters: ["com.android.server.pm."],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 // server os TEST_MAPPING
@@ -368,7 +353,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -390,14 +374,6 @@
 }
 
 test_module_config {
-    name: "FrameworksServicesTests_com_android_server_tare_Presubmit",
-    base: "FrameworksServicesTests",
-    test_suites: ["device-tests"],
-    include_filters: ["com.android.server.tare"],
-    exclude_annotations: FLAKY,
-}
-
-test_module_config {
     name: "FrameworksServicesTests_com_android_server_tare",
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
@@ -405,14 +381,6 @@
 }
 
 test_module_config {
-    name: "FrameworksServicesTests_com_android_server_usage_Presubmit",
-    base: "FrameworksServicesTests",
-    test_suites: ["device-tests"],
-    include_filters: ["com.android.server.usage"],
-    exclude_annotations: FLAKY,
-}
-
-test_module_config {
     name: "FrameworksServicesTests_com_android_server_usage",
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
@@ -427,14 +395,6 @@
 }
 
 test_module_config {
-    name: "FrameworksServicesTests_accessibility_Presubmit",
-    base: "FrameworksServicesTests",
-    test_suites: ["device-tests"],
-    include_filters: ["com.android.server.accessibility"],
-    exclude_annotations: FLAKY,
-}
-
-test_module_config {
     name: "FrameworksServicesTests_accessibility",
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
@@ -462,7 +422,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.am."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -485,7 +444,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.audio"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -501,7 +459,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.hdmi"],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -509,7 +466,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.hdmi"],
-    exclude_annotations: ["org.junit.Ignore"],
 }
 
 test_module_config {
@@ -524,7 +480,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.lights"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -540,7 +495,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.location.contexthub."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
@@ -548,15 +502,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.locksettings."],
-    exclude_annotations: FLAKY,
-}
-
-test_module_config {
-    name: "FrameworksServicesTests_android_server_logcat_Presubmit",
-    base: "FrameworksServicesTests",
-    test_suites: ["device-tests"],
-    include_filters: ["com.android.server.logcat"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -572,7 +517,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.net."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -602,7 +546,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.policy."],
     include_annotations: ["android.platform.test.annotations.Presubmit"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -624,7 +567,6 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.power.hint"],
-    exclude_annotations: FLAKY,
 }
 
 test_module_config {
@@ -654,7 +596,6 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.location.contexthub."],
     include_annotations: ["android.platform.test.annotations.Postsubmit"],
-    exclude_annotations: FLAKY_AND_IGNORED,
 }
 
 test_module_config {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2e6c93c..566feb7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -35,6 +35,7 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
+import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
 import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
 import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
 
@@ -2046,6 +2047,53 @@
     }
 
     @Test
+    public void showAccessibilityTargetSelection_navBarNavigationMode_softwareExtra() {
+        mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+                NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
+
+        mA11yms.notifyAccessibilityButtonLongClicked(Display.DEFAULT_DISPLAY);
+        mTestableLooper.processAllMessages();
+
+        assertStartActivityWithExpectedShortcutType(mTestableContext.getMockContext(), SOFTWARE);
+    }
+
+    @Test
+    @DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void showAccessibilityTargetSelection_gestureNavigationMode_softwareExtra() {
+        mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+                NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
+
+        mA11yms.notifyAccessibilityButtonLongClicked(Display.DEFAULT_DISPLAY);
+        mTestableLooper.processAllMessages();
+
+        assertStartActivityWithExpectedShortcutType(mTestableContext.getMockContext(), SOFTWARE);
+    }
+
+    @Test
+    @EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void showAccessibilityTargetSelection_gestureNavigationMode_gestureExtra() {
+        mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+                NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
+
+        mA11yms.notifyAccessibilityButtonLongClicked(Display.DEFAULT_DISPLAY);
+        mTestableLooper.processAllMessages();
+
+        assertStartActivityWithExpectedShortcutType(mTestableContext.getMockContext(), GESTURE);
+    }
+
+    @Test
     public void registerUserInitializationCompleteCallback_isRegistered() {
         mA11yms.mUserInitializationCompleteCallbacks.clear();
 
@@ -2075,6 +2123,43 @@
                 UserHandle.MIN_SECONDARY_USER_ID);
     }
 
+    @Test
+    @DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void getShortcutTypeForGenericShortcutCalls_softwareType() {
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+
+        assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
+                .isEqualTo(SOFTWARE);
+    }
+
+    @Test
+    @EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void getShortcutTypeForGenericShortcutCalls_gestureNavigationMode_gestureType() {
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+                NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
+
+        assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
+                .isEqualTo(GESTURE);
+    }
+
+    @Test
+    @EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void getShortcutTypeForGenericShortcutCalls_buttonNavigationMode_softwareType() {
+        final AccessibilityUserState userState = new AccessibilityUserState(
+                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+                NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
+
+        assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
+                .isEqualTo(SOFTWARE);
+    }
+
     private Set<String> readStringsFromSetting(String setting) {
         final Set<String> result = new ArraySet<>();
         mA11yms.readColonDelimitedSettingToSet(
@@ -2148,6 +2233,14 @@
                 Intent.EXTRA_COMPONENT_NAME)).isEqualTo(componentName);
     }
 
+    private void assertStartActivityWithExpectedShortcutType(Context mockContext,
+            @UserShortcutType int shortcutType) {
+        verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
+                any(Bundle.class), any(UserHandle.class));
+        assertThat(mIntentArgumentCaptor.getValue().getIntExtra(
+                EXTRA_TYPE_TO_CHOOSE, -1)).isEqualTo(shortcutType);
+    }
+
     private void setupShortcutTargetServices() {
         setupShortcutTargetServices(mA11yms.getCurrentUserState());
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 62fa951..627b5e3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -28,10 +28,13 @@
 import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
 import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
 import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -67,6 +70,8 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.After;
@@ -504,6 +509,36 @@
         assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
     }
 
+    @Test
+    public void isShortcutMagnificationEnabledLocked_anyShortcutType_returnsTrue() {
+        // Clear every shortcut
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+            setMagnificationForShortcutType(shortcutType, false);
+        }
+        // Check each shortcut individually
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+            // Setup
+            setMagnificationForShortcutType(shortcutType, true);
+
+            // Checking
+            assertThat(mUserState.getShortcutTargetsLocked(shortcutType))
+                    .containsExactly(MAGNIFICATION_CONTROLLER_NAME);
+            assertThat(mUserState.isShortcutMagnificationEnabledLocked()).isTrue();
+
+            // Cleanup
+            setMagnificationForShortcutType(shortcutType, false);
+        }
+    }
+
+    @Test
+    public void isShortcutMagnificationEnabledLocked_noShortcutTypes_returnsFalse() {
+        // Clear every shortcut
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+            setMagnificationForShortcutType(shortcutType, false);
+        }
+        assertThat(mUserState.isShortcutMagnificationEnabledLocked()).isFalse();
+    }
+
     private int getSecureIntForUser(String key, int userId) {
         return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
     }
@@ -511,4 +546,16 @@
     private void putSecureIntForUser(String key, int value, int userId) {
         Settings.Secure.putIntForUser(mMockResolver, key, value, userId);
     }
+
+    private void setMagnificationForShortcutType(
+            @UserShortcutType int shortcutType, boolean enabled) {
+        if (shortcutType == TRIPLETAP) {
+            mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(enabled);
+        } else if (shortcutType == TWOFINGER_DOUBLETAP) {
+            mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(enabled);
+        } else {
+            mUserState.updateShortcutTargetsLocked(
+                    enabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME) : Set.of(), shortcutType);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 6b8e414..b4b3612 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -558,7 +558,9 @@
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
-                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
+                eq(Flags.mandatoryBiometrics()
+                        ? BiometricConstants.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS
+                        : BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
                 eq(0 /* vendorCode */));
 
         // Enrolled, not disabled in settings, user requires confirmation in settings
@@ -1450,7 +1452,9 @@
     }
 
     @Test
-    public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
+    @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testCanAuthenticate_whenBiometricsNotEnabledForApps_returnsHardwareUnavailable()
+            throws Exception {
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
@@ -1468,6 +1472,25 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                .thenReturn(true);
+
+        // When only biometric is requested
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+    }
+
+    @Test
     public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
         mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 760d38e..b758f57 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
 
 import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
 
@@ -266,6 +267,29 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testCalculateByPriority()
+            throws Exception {
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+
+        BiometricSensor faceSensor = getFaceSensor();
+        BiometricSensor fingerprintSensor = getFingerprintSensor();
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(faceSensor, fingerprintSensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+        assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
+                BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testMandatoryBiometricsNegativeButtonText_whenSet()
             throws Exception {
         when(mTrustManager.isInSignificantPlace()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 004c6c6..21129a7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.MESSAGE_DEVICE_VENDOR_ID;
@@ -529,21 +530,32 @@
     public void handleVendorCommand_notHandled() {
         HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommand(ADDR_TV,
                 ADDR_PLAYBACK_1, new byte[]{0});
-        mNativeWrapper.onCecMessage(vendorCommand);
+        @Constants.HandleMessageResult int result =
+                mHdmiLocalDevice.handleVendorCommand(vendorCommand);
         mTestLooper.dispatchAll();
 
-        HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV,
-                vendorCommand.getOpcode(), Constants.ABORT_REFUSED);
+        assertEquals(Constants.ABORT_REFUSED, result);
     }
 
     @Test
     public void handleVendorCommandWithId_notHandled_Cec14() {
         HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommandWithId(ADDR_TV,
                 ADDR_PLAYBACK_1, 0x1234, new byte[]{0});
-        mNativeWrapper.onCecMessage(vendorCommand);
+        @Constants.HandleMessageResult int result =
+                mHdmiLocalDevice.handleVendorCommandWithId(vendorCommand);
         mTestLooper.dispatchAll();
 
-        HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV,
-                vendorCommand.getOpcode(), Constants.ABORT_REFUSED);
+        assertEquals(Constants.ABORT_REFUSED, result);
+    }
+
+    @Test
+    public void handleVendorCommandWithId_broadcasted_handled() {
+        HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommandWithId(ADDR_TV,
+                ADDR_BROADCAST, 0x1234, new byte[]{0});
+        @Constants.HandleMessageResult int result =
+                mHdmiLocalDevice.handleVendorCommandWithId(vendorCommand);
+        mTestLooper.dispatchAll();
+
+        assertEquals(Constants.HANDLED, result);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index cbf7935..def3355 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -19,7 +19,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
+import android.os.UserHandle;
 import android.webkit.UserPackage;
 import android.webkit.WebViewProviderInfo;
 
@@ -137,16 +137,12 @@
         List<UserPackage> ret = new ArrayList();
         // Loop over defined users, and find the corresponding package for each user.
         for (int userId : mUsers) {
-            ret.add(new UserPackage(createUserInfo(userId),
+            ret.add(new UserPackage(UserHandle.of(userId),
                     userPackages == null ? null : userPackages.get(userId)));
         }
         return ret;
     }
 
-    private static UserInfo createUserInfo(int userId) {
-        return new UserInfo(userId, "User nr. " + userId, 0 /* flags */);
-    }
-
     /**
      * Set package for primary user.
      */
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index f572e7a..50a5f65 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -62,6 +62,8 @@
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
+import android.media.RingtoneManager;
+import android.media.Utils;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Build;
@@ -69,6 +71,7 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
@@ -93,6 +96,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.util.ArrayList;
 
 @SmallTest
@@ -141,6 +147,7 @@
 
         when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator);
         when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+        when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO);
         final Resources res = mContext.getResources();
         when(mMockContext.getResources()).thenReturn(res);
         when(mMockContext.getPackageManager()).thenReturn(mPm);
@@ -511,6 +518,51 @@
     }
 
     @Test
+    @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
+    public void testVibration_customVibrationForSound_withoutVibrationUri() {
+        // prepare testing data
+        Uri backupDefaultUri = RingtoneManager.getActualDefaultRingtoneUri(mMockContext,
+                RingtoneManager.TYPE_NOTIFICATION);
+        RingtoneManager.setActualDefaultRingtoneUri(mMockContext, RingtoneManager.TYPE_NOTIFICATION,
+                Settings.System.DEFAULT_NOTIFICATION_URI);
+        defaultChannel.enableVibration(true);
+        defaultChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, CUSTOM_ATTRIBUTES);
+        StatusBarNotification sbn = getNotification(
+                /* channelVibrationPattern= */ null,
+                /* channelVibrationEffect= */ null,
+                /* insistent= */ false);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+        try {
+            assertEquals(
+                    new VibratorHelper(mMockContext).createDefaultVibration(false),
+                    record.getVibration());
+        } finally {
+            // restore the data
+            RingtoneManager.setActualDefaultRingtoneUri(mMockContext,
+                    RingtoneManager.TYPE_NOTIFICATION,
+                    backupDefaultUri);
+        }
+    }
+
+    @Test
+    @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
+    public void testVibration_customVibrationForSound_withVibrationUri() throws IOException {
+        defaultChannel.enableVibration(true);
+        VibrationInfo vibration = getTestingVibration(mVibrator);
+        Uri uriWithVibration = getVibrationUriAppended(
+                Settings.System.DEFAULT_NOTIFICATION_URI, vibration.mUri);
+        defaultChannel.setSound(uriWithVibration, CUSTOM_ATTRIBUTES);
+        StatusBarNotification sbn = getNotification(
+                /* channelVibrationPattern= */ null,
+                /* channelVibrationEffect= */ null,
+                /* insistent= */ false);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+        assertEquals(vibration.mVibrationEffect, record.getVibration());
+    }
+
+    @Test
     public void testImportance_preUpgrade() {
         StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
@@ -1594,4 +1646,39 @@
 
         assertThat(record.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
     }
+
+    static class VibrationInfo {
+        public VibrationEffect mVibrationEffect;
+        public Uri mUri;
+        VibrationInfo(VibrationEffect vibrationEffect, Uri uri) {
+            mVibrationEffect = vibrationEffect;
+            mUri = uri;
+        }
+    }
+
+    private static VibrationInfo getTestingVibration(Vibrator vibrator) throws IOException {
+        File tempVibrationFile = File.createTempFile("test_vibration_file", ".xml");
+        FileWriter writer = new FileWriter(tempVibrationFile);
+        writer.write("<vibration-effect>\n"
+                + "    <waveform-effect>\n"
+                + "        <!-- PRIMING -->\n"
+                + "        <waveform-entry durationMs=\"0\" amplitude=\"0\"/>\n"
+                + "        <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n"
+                + "        <waveform-entry durationMs=\"250\" amplitude=\"0\"/>\n"
+                + "        <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n"
+                + "        <waveform-entry durationMs=\"500\" amplitude=\"0\"/>\n"
+                + "    </waveform-effect>\n"
+                + "</vibration-effect>"); // Your test XML content
+        writer.close();
+        Uri vibrationUri = Uri.parse(tempVibrationFile.toURI().toString());
+
+        VibrationEffect vibrationEffect = Utils.parseVibrationEffect(vibrator, vibrationUri);
+        return new VibrationInfo(vibrationEffect, vibrationUri);
+    }
+
+    private static Uri getVibrationUriAppended(Uri audioUri, Uri vibrationUri) {
+        Uri.Builder builder = audioUri.buildUpon();
+        builder.appendQueryParameter(Utils.VIBRATION_URI_PARAM, vibrationUri.toString());
+        return builder.build();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index 0993bec..4d2396c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -22,8 +22,12 @@
 
 import static org.mockito.Mockito.when;
 
+import android.media.Utils;
+import android.net.Uri;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -36,6 +40,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class VibratorHelperTest extends UiServiceTestCase {
@@ -86,7 +94,34 @@
     }
 
     @Test
+    public void createVibrationEffectFromSoundUri_nullInput() {
+        assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(null));
+    }
+
+    @Test
+    public void createVibrationEffectFromSoundUri_emptyUri() {
+        assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(Uri.EMPTY));
+    }
+
+    @Test
+    public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() {
+        Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI;
+        assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
+    }
+
+    @Test
+    public void createVibrationEffectFromSoundUri_uriWithVibrationUri() throws IOException {
+        // prepare the uri with vibration
+        when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO);
+        Uri validUri = getVibrationUriAppended(Settings.System.DEFAULT_NOTIFICATION_URI);
+
+        assertSingleVibration(mVibratorHelper.createVibrationEffectFromSoundUri(validUri));
+    }
+
+    @Test
     public void createVibration_insistent_createsRepeatingVibration() {
+        when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO);
+
         when(mVibrator.hasFrequencyControl()).thenReturn(false);
         assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true));
         assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true));
@@ -98,6 +133,8 @@
 
     @Test
     public void createVibration_nonInsistent_createsSingleShotVibration() {
+        when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO);
+
         when(mVibrator.hasFrequencyControl()).thenReturn(false);
         assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false));
         assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false));
@@ -120,4 +157,26 @@
                 effect instanceof VibrationEffect.Composed);
         return ((VibrationEffect.Composed) effect).getRepeatIndex();
     }
+
+    private static Uri getVibrationUriAppended(Uri baseUri) throws IOException {
+        File tempVibrationFile = File.createTempFile("test_vibration_file", ".xml");
+        FileWriter writer = new FileWriter(tempVibrationFile);
+        writer.write("<vibration-effect>\n"
+                + "    <waveform-effect>\n"
+                + "        <!-- PRIMING -->\n"
+                + "        <waveform-entry durationMs=\"0\" amplitude=\"0\"/>\n"
+                + "        <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n"
+                + "        <waveform-entry durationMs=\"250\" amplitude=\"0\"/>\n"
+                + "        <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n"
+                + "        <waveform-entry durationMs=\"500\" amplitude=\"0\"/>\n"
+                + "    </waveform-effect>\n"
+                + "</vibration-effect>"); // Your test XML content
+        writer.close();
+
+        Uri.Builder builder = baseUri.buildUpon();
+        builder.appendQueryParameter(
+                Utils.VIBRATION_URI_PARAM,
+                tempVibrationFile.toURI().toString());
+        return builder.build();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 2e488d8..09fe75d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
@@ -78,7 +79,6 @@
 
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -86,7 +86,6 @@
 import com.android.server.testutils.TestHandler;
 import com.android.server.wm.DisplayContent.FixedRotationTransitionListener;
 
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -168,25 +167,10 @@
     public void setUp() {
         FakeSettingsProvider.clearSettingsProvider();
 
-        mPreviousStatusBarManagerInternal = LocalServices.getService(
-                StatusBarManagerInternal.class);
-        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
-        mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
-        LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
         mDisplayRotationImmersiveAppCompatPolicyMock = null;
         mBuilder = new DisplayRotationBuilder();
     }
 
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
-        if (mPreviousStatusBarManagerInternal != null) {
-            LocalServices.addService(StatusBarManagerInternal.class,
-                    mPreviousStatusBarManagerInternal);
-            mPreviousStatusBarManagerInternal = null;
-        }
-    }
-
     // ================================
     // Display Settings Related Tests
     // ================================
@@ -677,7 +661,8 @@
         mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
         assertTrue(waitForUiHandler());
 
-        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(DEFAULT_DISPLAY,
+                Surface.ROTATION_90, true);
     }
 
     @Test
@@ -697,7 +682,8 @@
         mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
         assertTrue(waitForUiHandler());
 
-        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(DEFAULT_DISPLAY,
+                Surface.ROTATION_90, true);
 
         // An imaginary ActivityRecord.setRequestedOrientation call disables immersive mode:
         when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced(
@@ -728,7 +714,7 @@
         assertTrue(waitForUiHandler());
 
         verify(mMockStatusBarManagerInternal)
-                .onProposedRotationChanged(Surface.ROTATION_180, true);
+                .onProposedRotationChanged(DEFAULT_DISPLAY, Surface.ROTATION_180, true);
     }
 
     @Test
@@ -746,7 +732,7 @@
         assertTrue(waitForUiHandler());
 
         verify(mMockStatusBarManagerInternal)
-                .onProposedRotationChanged(Surface.ROTATION_180, false);
+                .onProposedRotationChanged(DEFAULT_DISPLAY, Surface.ROTATION_180, false);
     }
 
     @Test
@@ -773,7 +759,7 @@
         assertTrue(waitForUiHandler());
 
         verify(mMockStatusBarManagerInternal)
-                .onProposedRotationChanged(Surface.ROTATION_180, false);
+                .onProposedRotationChanged(DEFAULT_DISPLAY, Surface.ROTATION_180, false);
     }
 
     @Test
@@ -1526,6 +1512,7 @@
 
             mMockDisplayContent = mock(DisplayContent.class);
             when(mMockDisplayContent.getDisplay()).thenReturn(mock(Display.class));
+            when(mMockDisplayContent.getDisplayId()).thenReturn(DEFAULT_DISPLAY);
             mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
             when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt()))
                     .thenReturn(NO_CUTOUT);
@@ -1541,6 +1528,9 @@
             field.set(mMockDisplayContent, mock(FixedRotationTransitionListener.class));
 
             mMockDisplayPolicy = mock(DisplayPolicy.class);
+            mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
+            when(mMockDisplayPolicy.getStatusBarManagerInternal()).thenReturn(
+                    mMockStatusBarManagerInternal);
 
             mMockRes = mock(Resources.class);
             when(mMockContext.getResources()).thenReturn((mMockRes));
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 1d14dc3..bef4531 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -219,7 +219,7 @@
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN a pinning request should be shown
-        verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt());
+        verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt(), anyInt());
     }
 
     @Test
@@ -769,9 +769,11 @@
 
     private Task getTask(Intent intent, int lockTaskAuth) {
         Task tr = mock(Task.class);
+        DisplayContent dc = mock(DisplayContent.class);
         tr.mLockTaskAuth = lockTaskAuth;
         tr.intent = intent;
         tr.mUserId = TEST_USER_ID;
+        tr.mDisplayContent = dc;
         return tr;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 52a80b0..f8e42d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -51,6 +51,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
@@ -140,8 +141,7 @@
     }
 
     private Transition createTestTransition(int transitType) {
-        final TransitionController controller = new TestTransitionController(
-                mock(ActivityTaskManagerService.class));
+        final TransitionController controller = new TestTransitionController(mAtm);
 
         mSyncEngine = createTestBLASTSyncEngine();
         controller.setSyncEngine(mSyncEngine);
@@ -2357,7 +2357,7 @@
     }
 
     @Test
-    public void testMoveToTopWhileVisible() {
+    public void testMoveTaskToTopWhileVisible() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
         final ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -2392,6 +2392,55 @@
         assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+    public void testMoveDisplayToTop() {
+        // Set up two displays, each of which has a task.
+        DisplayContent otherDisplay = createNewDisplay();
+        final Consumer<DisplayContent> setUpTask = (DisplayContent dc) -> {
+            final Task task = createTask(dc);
+            final ActivityRecord act = createActivityRecord(task);
+            final TestWindowState win = createWindowState(
+                    new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), act);
+            act.addWindow(win);
+            act.setVisibleRequested(true);
+        };
+        setUpTask.accept(mDisplayContent);
+        setUpTask.accept(otherDisplay);
+        mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /* updateImWindows */);
+
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        // Emulate WindowManagerService#moveDisplayToTopInternal().
+        transition.recordTaskOrder(mDefaultDisplay);
+        mDefaultDisplay.getParent().positionChildAt(WindowContainer.POSITION_TOP,
+                mDefaultDisplay, true /* includingParents */);
+        mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /* updateImWindows */);
+        transition.setReady(mDefaultDisplay, true /* ready */);
+
+        // Test has order changes, a shallow check of order changes.
+        assertTrue(transition.hasOrderChanges());
+
+        // We just moved a display to top, so there shouldn't be any changes.
+        ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
+                participants, changes);
+        assertTrue(targets.isEmpty());
+
+        // After collecting order changes, the task on the newly focused display should be
+        // considered to get moved to top.
+        transition.collectOrderChanges(true);
+        targets = Transition.calculateTargets(participants, changes);
+        assertEquals(1, targets.size());
+
+        // Make sure the flag is set
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, mMockT);
+        assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
+        assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
+    }
+
     private class OrderChangeTestSetup {
         final TransitionController mController;
         final TestTransitionPlayer mPlayer;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 112471b..c85374e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -376,7 +376,8 @@
      */
     void requestLogMark(in String message);
 
-    void setTestPhoneAcctSuggestionComponent(String flattenedComponentName);
+    void setTestPhoneAcctSuggestionComponent(String flattenedComponentName,
+        in UserHandle userHandle);
 
     void setTestDefaultCallScreeningApp(String packageName);
 
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 0f08be2..2a49eb1 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -296,33 +296,31 @@
 
     @Test
     public void testTouchpadClick() {
-        View child;
+        View child = mTouchpadDebugView.getChildAt(0);
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
-                        new TouchpadFingerState[0]));
+                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) {
-            child = mTouchpadDebugView.getChildAt(i);
-            assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
-        }
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
-                        new TouchpadFingerState[0]));
+                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) {
-            child = mTouchpadDebugView.getChildAt(i);
-            assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED);
-        }
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED);
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
-                        new TouchpadFingerState[0]));
+                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) {
-            child = mTouchpadDebugView.getChildAt(i);
-            assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
-        }
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+
+        // Color should not change because hardware state of a different touchpad
+        mTouchpadDebugView.updateHardwareState(
+                new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
+                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
+
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 0439d5f5..edad678 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,7 +123,6 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
         mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
         mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
-        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_DISABLE_IPSEC_LOSS_DETECTOR);
 
         when(mNetwork.getNetId()).thenReturn(-1);