Merge "Make DEBUG not static" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08a09e1..b1f587e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -334,7 +334,7 @@
 aconfig_declarations {
     name: "android.app.flags-aconfig",
     package: "android.app",
-    srcs: ["core/java/android/app/activity_manager.aconfig"],
+    srcs: ["core/java/android/app/*.aconfig"],
 }
 
 java_aconfig_library {
diff --git a/Android.bp b/Android.bp
index f1a3af2..b1b332a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,6 +389,7 @@
     static_libs: [
         "android.hardware.common.fmq-V1-java",
         "bouncycastle-repackaged-unbundled",
+        "com.android.sysprop.foldlockbehavior",
         "framework-internal-utils",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
@@ -525,6 +526,7 @@
     required: [
         "framework-minus-apex",
         "framework-platform-compat-config",
+        "framework-location-compat-config",
         "services-platform-compat-config",
         "icu4j-platform-compat-config",
         "TeleService-platform-compat-config",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 122e627..b215ee4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -68,7 +68,6 @@
       "name": "FrameworksInputMethodSystemServerTests",
       "options": [
         {"include-filter": "com.android.server.inputmethod"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
index b8fef63..6924cb2 100644
--- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -7,7 +7,6 @@
       ],
       "options": [
         {"include-filter": "com.android.server.DeviceIdleControllerTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     },
@@ -29,4 +28,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
index b76c582..c0686116 100644
--- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "FrameworksMockingServicesTests",
       "options": [
         {"include-filter": "com.android.server.DeviceIdleControllerTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
@@ -17,4 +16,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index 8504b1f..e649485 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -3,8 +3,6 @@
         {
             "name": "CtsJobSchedulerTestCases",
             "options": [
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.LargeTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.LargeTest"}
             ]
@@ -13,18 +11,16 @@
             "name": "FrameworksMockingServicesTests",
             "options": [
                 {"include-filter": "com.android.server.job"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.LargeTest"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+                {"exclude-annotation": "androidx.test.filters.LargeTest"}
             ]
         },
         {
             "name": "FrameworksServicesTests",
             "options": [
                 {"include-filter": "com.android.server.job"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.LargeTest"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+                {"exclude-annotation": "androidx.test.filters.LargeTest"}
             ]
         }
     ],
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
index 73b00b6..e194b8d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
@@ -4,7 +4,6 @@
             "name": "FrameworksMockingServicesTests",
             "options": [
                 {"include-filter": "com.android.server.tare"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
         },
@@ -12,7 +11,6 @@
             "name": "FrameworksServicesTests",
             "options": [
                 {"include-filter": "com.android.server.tare"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
         }
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 9ec799f..a75415e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "CtsUsageStatsTestCases",
       "options": [
         {"include-filter": "android.app.usage.cts.UsageStatsTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.MediumTest"},
         {"exclude-annotation": "androidx.test.filters.LargeTest"}
@@ -21,7 +20,6 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.usage"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
@@ -37,4 +35,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/api/Android.bp b/api/Android.bp
index 6986ac0..f017a47 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -91,6 +91,7 @@
         "framework-media",
         "framework-mediaprovider",
         "framework-ondevicepersonalization",
+        "framework-pdf",
         "framework-permission",
         "framework-permission-s",
         "framework-scheduling",
diff --git a/boot/Android.bp b/boot/Android.bp
index 93d425e..b33fab6 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -208,6 +208,13 @@
     ],
 }
 
+genrule { // This module exists to make the srcjar output available to Make.
+    name: "platform-bootclasspath.srcjar",
+    srcs: [":platform-bootclasspath{.srcjar}"],
+    out: ["platform-bootclasspath.srcjar"],
+    cmd: "cp $(in) $(out)",
+}
+
 platform_systemserverclasspath {
     name: "platform-systemserverclasspath",
 }
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 82df555..e7361fe 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -48,6 +48,7 @@
 | `vid`            | 16-bit integer | Vendor ID                  |
 | `pid`            | 16-bit integer | Product ID                 |
 | `bus`            | string         | Bus that device should use |
+| `port`           | string         | `phys` value to report     |
 | `configuration`  | object array   | uinput device configuration|
 | `ff_effects_max` | integer        | `ff_effects_max` value     |
 | `abs_info`       | array          | Absolute axes information  |
diff --git a/core/api/current.txt b/core/api/current.txt
index c9f0639..7fd25b2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5952,6 +5952,7 @@
 
   public class GrammaticalInflectionManager {
     method public int getApplicationGrammaticalGender();
+    method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
     method public void setRequestedApplicationGrammaticalGender(int);
   }
 
@@ -42903,7 +42904,7 @@
     field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
-    field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -43071,6 +43072,7 @@
     field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -54454,8 +54456,8 @@
   public class AnimationUtils {
     ctor public AnimationUtils();
     method public static long currentAnimationTimeMillis();
-    method public static long getExpectedPresentationTimeMillis();
-    method public static long getExpectedPresentationTimeNanos();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
     method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -55027,10 +55029,12 @@
     method public int getInitialToolType();
     method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
     method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
+    method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
     method public final void makeCompatible(int);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
     method public void setInitialSurroundingText(@NonNull CharSequence);
     method public void setInitialToolType(int);
+    method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean);
     method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
     method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d88e00..8748e69 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3223,14 +3223,14 @@
 
   public final class VirtualDeviceParams implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
-    method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
+    method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
+    method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
     method public int getAudioPlaybackSessionId();
     method public int getAudioRecordingSessionId();
-    method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
-    method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
-    method public int getDefaultActivityPolicy();
-    method public int getDefaultNavigationPolicy();
+    method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
+    method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
+    method @Deprecated public int getDefaultActivityPolicy();
+    method @Deprecated public int getDefaultNavigationPolicy();
     method public int getDevicePolicy(int);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
     method public int getLockState();
@@ -3238,15 +3238,15 @@
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
     method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
-    field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+    field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+    field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
     field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
     field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
     field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
-    field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
-    field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+    field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+    field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
     field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
@@ -3257,12 +3257,12 @@
     ctor public VirtualDeviceParams.Builder();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
-    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
-    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+    method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int);
-    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
-    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+    method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
@@ -16731,17 +16731,22 @@
   }
 
   public final class SatelliteManager {
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -16768,6 +16773,7 @@
     field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
     field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
     field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 183783b..9a90df9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -263,6 +263,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TimeZone;
@@ -3033,7 +3034,7 @@
             "%13s %8s %8s %8s %8s %8s %8s %8s %8s";
     private static final String ONE_COUNT_COLUMN = "%21s %8d";
     private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
-    private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8s %21s %8d";
+    private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8d %21s %8d";
     private static final String TWO_COUNT_COLUMN_HEADER = "%21s %8s %21s %8s";
     private static final String ONE_ALT_COUNT_COLUMN = "%21s %8s %21s %8d";
 
@@ -3041,7 +3042,7 @@
     private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4;
 
     static void printRow(PrintWriter pw, String format, Object...objs) {
-        pw.println(String.format(format, objs));
+        pw.println(String.format(Locale.US, format, objs));
     }
 
     @NeverCompile
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4f9225f..ca10d14 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2336,6 +2336,7 @@
             OP_WRITE_MEDIA_VIDEO,
             OP_READ_MEDIA_IMAGES,
             OP_WRITE_MEDIA_IMAGES,
+            OP_READ_MEDIA_VISUAL_USER_SELECTED,
             // Nearby devices
             OP_BLUETOOTH_SCAN,
             OP_BLUETOOTH_CONNECT,
@@ -2376,7 +2377,6 @@
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
             OP_RUN_USER_INITIATED_JOBS,
-            OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OP_USE_FULL_SCREEN_INTENT
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 1905b6a..bc6fe61 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,12 +16,15 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -31,11 +34,15 @@
  */
 @SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
 public class GrammaticalInflectionManager {
-    private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+
+    /** @hide */
+    @NonNull
+    public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
             Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
             Configuration.GRAMMATICAL_GENDER_NEUTRAL,
             Configuration.GRAMMATICAL_GENDER_FEMININE,
-            Configuration.GRAMMATICAL_GENDER_MASCULINE));
+            Configuration.GRAMMATICAL_GENDER_MASCULINE)));
 
     private final Context mContext;
     private final IGrammaticalInflectionManager mService;
@@ -79,7 +86,7 @@
      */
     public void setRequestedApplicationGrammaticalGender(
             @Configuration.GrammaticalGender int grammaticalGender) {
-        if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+        if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
             throw new IllegalArgumentException("Unknown grammatical gender");
         }
 
@@ -90,4 +97,44 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the current grammatical gender for all privileged applications. The value will be
+     * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)
+     *
+     * @param grammaticalGender the terms of address the user preferred in system.
+     *
+     * @see Configuration#getGrammaticalGender
+     * @hide
+     */
+    public void setSystemWideGrammaticalGender(
+            @Configuration.GrammaticalGender int grammaticalGender) {
+        if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        try {
+            mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current grammatical gender of privileged application from the encrypted file,
+     * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}.
+     *
+     * @return the value of grammatical gender
+     *
+     * @see Configuration#getGrammaticalGender
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
+    @Configuration.GrammaticalGender
+    public int getSystemGrammaticalGender() {
+        try {
+            return mService.getSystemGrammaticalGender(mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 9366a45..48a4841 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -16,4 +16,14 @@
       * Sets a specified app’s app-specific grammatical gender.
       */
      void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
- }
\ No newline at end of file
+
+     /**
+      * Sets the grammatical gender to system.
+      */
+     void setSystemWideGrammaticalGender(int userId, int gender);
+
+     /**
+      * Gets the grammatical gender from system.
+      */
+     int getSystemGrammaticalGender(int userId);
+ }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d9..9cf54e3 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@
 per-file Broadcast* = file:/BROADCASTS_OWNERS
 per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
 
+# GrammaticalInflectionManager
+per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+
 # KeyguardManager
 per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
 
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 315a055..a29c196 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -19,7 +19,7 @@
             "name": "CtsAppOpsTestCases",
             "options": [
                 {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
                 }
             ]
         },
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
new file mode 100644
index 0000000..989ce61
--- /dev/null
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+    name: "system_terms_of_address_enabled"
+    namespace: "grammatical_gender"
+    description: "Feature flag for System Terms of Address"
+    bug: "297798866"
+}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index bf00a5a..2f97080 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,6 +23,8 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
 import android.content.ComponentName;
 import android.content.IntentFilter;
 import android.graphics.Point;
@@ -232,4 +234,10 @@
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
+
+    /**
+     * Creates a new VirtualCamera and registers it with the VirtualCameraProvider.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void registerVirtualCamera(in IVirtualCamera camera);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 7bf2e91..f6a7d2a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -22,6 +22,8 @@
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.companion.virtual.audio.VirtualAudioDevice;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.ComponentName;
 import android.content.Context;
@@ -339,6 +341,11 @@
     }
 
     @NonNull
+    VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
+        return new VirtualCamera(mVirtualDevice, config);
+    }
+
+    @NonNull
     void setShowPointerIcon(boolean showPointerIcon) {
         try {
             mVirtualDevice.setShowPointerIcon(showPointerIcon);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d338d17..baed7f9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -33,6 +33,8 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.ComponentName;
@@ -851,6 +853,24 @@
         }
 
         /**
+         * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
+         *
+         * @param config camera config.
+         * @return newly created camera;
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+        public VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
+            if (!Flags.virtualCamera()) {
+                throw new UnsupportedOperationException(
+                        "Flag is not enabled: %s".formatted(Flags.FLAG_VIRTUAL_CAMERA));
+            }
+            return mVirtualDeviceInternal.createVirtualCamera(Objects.requireNonNull(config));
+        }
+
+        /**
          * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
          *
          * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0fa78c8..0975cbb 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -94,13 +94,19 @@
     /**
      * Indicates that activities are allowed by default on this virtual device, unless they are
      * explicitly blocked by {@link Builder#setBlockedActivities}.
+     *
+     * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
      */
+    @Deprecated
     public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0;
 
     /**
      * Indicates that activities are blocked by default on this virtual device, unless they are
      * allowed by {@link Builder#setAllowedActivities}.
+     *
+     * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
      */
+    @Deprecated
     public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1;
 
     /** @hide */
@@ -113,13 +119,19 @@
     /**
      * Indicates that tasks are allowed to navigate to other tasks on this virtual device,
      * unless they are explicitly blocked by {@link Builder#setBlockedCrossTaskNavigations}.
+     *
+     * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
      */
+    @Deprecated
     public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0;
 
     /**
      * Indicates that tasks are blocked from navigating to other tasks by default on this virtual
      * device, unless allowed by {@link Builder#setAllowedCrossTaskNavigations}.
+     *
+     * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
      */
+    @Deprecated
     public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
 
     /** @hide */
@@ -325,7 +337,10 @@
      * be be allowed by default.
      *
      * @see Builder#setAllowedCrossTaskNavigations(Set)
+     *
+     * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
      */
+    @Deprecated
     @NonNull
     public Set<ComponentName> getAllowedCrossTaskNavigations() {
         return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
@@ -340,7 +355,10 @@
      * will be be allowed by default.
      *
      * @see Builder#setBlockedCrossTaskNavigations(Set)
+     *
+     * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
      */
+    @Deprecated
     @NonNull
     public Set<ComponentName> getBlockedCrossTaskNavigations() {
         return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
@@ -355,7 +373,10 @@
      *
      * @see Builder#setAllowedCrossTaskNavigations
      * @see Builder#setBlockedCrossTaskNavigations
+     *
+     * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
      */
+    @Deprecated
     @NavigationPolicy
     public int getDefaultNavigationPolicy() {
         return mDefaultNavigationPolicy;
@@ -366,7 +387,10 @@
      * allowed, except the ones explicitly blocked.
      *
      * @see Builder#setAllowedActivities(Set)
+     *
+     * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
      */
+    @Deprecated
     @NonNull
     public Set<ComponentName> getAllowedActivities() {
         return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
@@ -379,7 +403,10 @@
      * that all activities in {@link #getAllowedActivities} are allowed.
      *
      * @see Builder#setBlockedActivities(Set)
+     *
+     * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
      */
+    @Deprecated
     @NonNull
     public Set<ComponentName> getBlockedActivities() {
         return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
@@ -394,7 +421,10 @@
      *
      * @see Builder#setBlockedActivities
      * @see Builder#setAllowedActivities
+     *
+     * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
      */
+    @Deprecated
     @ActivityPolicy
     public int getDefaultActivityPolicy() {
         return mDefaultActivityPolicy;
@@ -743,7 +773,11 @@
          *
          * @param allowedCrossTaskNavigations A set of tasks {@link ComponentName} allowed to
          *   navigate to new tasks in the virtual device.
+         *
+         * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+         *   {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
          */
+        @Deprecated
         @NonNull
         public Builder setAllowedCrossTaskNavigations(
                 @NonNull Set<ComponentName> allowedCrossTaskNavigations) {
@@ -774,7 +808,11 @@
          *
          * @param blockedCrossTaskNavigations A set of tasks {@link ComponentName} to be
          * blocked from navigating to new tasks in the virtual device.
+         *
+         * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+         *   {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
          */
+        @Deprecated
         @NonNull
         public Builder setBlockedCrossTaskNavigations(
                 @NonNull Set<ComponentName> blockedCrossTaskNavigations) {
@@ -802,7 +840,11 @@
          *
          * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
          *   in the virtual device.
+         *
+         * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+         *   {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
          */
+        @Deprecated
         @NonNull
         public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
             if (mDefaultActivityPolicyConfigured
@@ -828,7 +870,11 @@
          *
          * @param blockedActivities A set of {@link ComponentName} to be blocked launching from
          *   virtual device.
+         *
+         * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+         *   {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
          */
+        @Deprecated
         @NonNull
         public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
             if (mDefaultActivityPolicyConfigured
diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
new file mode 100644
index 0000000..58b850d
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
@@ -0,0 +1,17 @@
+package android.companion.virtual.camera;
+
+import android.companion.virtual.camera.IVirtualCameraSession;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+
+/**
+ * Counterpart of ICameraDevice for virtual camera.
+ *
+ * @hide
+ */
+interface IVirtualCamera {
+
+    IVirtualCameraSession open();
+
+    VirtualCameraHalConfig getHalConfig();
+
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
new file mode 100644
index 0000000..2529807
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.companion.virtual.camera;
+
+/**
+ * Counterpart of ICameraDeviceSession for virtual camera.
+ *
+ * @hide
+ */
+interface IVirtualCameraSession {
+
+    void configureStream(int width, int height, int format);
+
+    void close();
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
new file mode 100644
index 0000000..791bf0a
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -0,0 +1,76 @@
+/*
+ * 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.companion.virtual.camera;
+
+import android.companion.virtual.IVirtualDevice;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Virtual camera that is used to send image data into system.
+ *
+ * @hide
+ */
+public final class VirtualCamera extends IVirtualCamera.Stub {
+
+    private final VirtualCameraConfig mConfig;
+
+    /**
+     * VirtualCamera device constructor.
+     *
+     * @param virtualDevice The Binder object representing this camera in the server.
+     * @param config Configuration for the new virtual camera
+     */
+    public VirtualCamera(
+            @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
+        mConfig = Objects.requireNonNull(config);
+        Objects.requireNonNull(virtualDevice);
+        try {
+            virtualDevice.registerVirtualCamera(this);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Get the camera session associated with this device */
+    @Override
+    public IVirtualCameraSession open() {
+        // TODO: b/302255544 - Make this async.
+        VirtualCameraSession session = mConfig.getCallback().onOpenSession();
+        return new VirtualCameraSessionInternal(session);
+    }
+
+    /** Returns the configuration of this virtual camera instance. */
+    @NonNull
+    public VirtualCameraConfig getConfig() {
+        return mConfig;
+    }
+
+    /**
+     * Returns the configuration to be used by the virtual camera HAL.
+     *
+     * @hide
+     */
+    @Override
+    @NonNull
+    public VirtualCameraHalConfig getHalConfig() {
+        return mConfig.getHalConfig();
+    }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
new file mode 100644
index 0000000..a7c3d4f
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.hardware.camera2.params.SessionConfiguration;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks
+ * from the framework and the camera system.
+ *
+ * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback)
+ * @hide
+ */
+public interface VirtualCameraCallback {
+
+    /**
+     * Called when a client opens a new camera session for the associated {@link VirtualCamera}
+     *
+     * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)
+     */
+    VirtualCameraSession onOpenSession();
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
new file mode 100644
index 0000000..fb464d5
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -0,0 +1,174 @@
+/*
+ * 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.companion.virtual.camera;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.graphics.ImageFormat;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration to create a new {@link VirtualCamera}.
+ *
+ * <p>Instance of this class are created using the {@link VirtualCameraConfig.Builder}.
+ *
+ * @hide
+ */
+public final class VirtualCameraConfig {
+
+    private final String mDisplayName;
+    private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
+    private final VirtualCameraCallback mCallback;
+    private final Executor mCallbackExecutor;
+
+    /**
+     * Builder for {@link VirtualCameraConfig}.
+     *
+     * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
+     * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}.
+     * <li>A name must be set with {@link #setDisplayName(String)}
+     * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)}
+     */
+    public static final class Builder {
+
+        private String mDisplayName;
+        private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>();
+        private Executor mCallbackExecutor;
+        private VirtualCameraCallback mCallback;
+
+        /** Set the visible name of this camera for the user. */
+        // TODO: b/290172356 - Take a resource id instead of displayName
+        @NonNull
+        public Builder setDisplayName(@NonNull String displayName) {
+            mDisplayName = requireNonNull(displayName);
+            return this;
+        }
+
+        /**
+         * Add an available stream configuration fot this {@link VirtualCamera}.
+         *
+         * <p>At least one {@link VirtualCameraStreamConfig} must be added.
+         *
+         * @param width The width of the stream
+         * @param height The height of the stream
+         * @param format The {@link ImageFormat} of the stream
+         */
+        @NonNull
+        public Builder addStreamConfiguration(
+                int width, int height, @ImageFormat.Format int format) {
+            VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig();
+            streamConfig.width = width;
+            streamConfig.height = height;
+            streamConfig.format = format;
+            mStreamConfiguration.add(streamConfig);
+            return this;
+        }
+
+        /**
+         * Sets the {@link VirtualCameraCallback} used by the framework to communicate with the
+         * {@link VirtualCamera} owner.
+         *
+         * <p>Setting a callback is mandatory.
+         *
+         * @param executor The executor onto which the callback methods will be called
+         * @param callback The instance of the callback to be added. Subsequent call to this method
+         *     will replace the callback set.
+         */
+        public Builder setCallback(
+                @NonNull Executor executor, @NonNull VirtualCameraCallback callback) {
+            mCallbackExecutor = requireNonNull(executor);
+            mCallback = requireNonNull(callback);
+            return this;
+        }
+
+        /**
+         * Builds a new instance of {@link VirtualCameraConfig}
+         *
+         * @throws NullPointerException if some required parameters are missing.
+         */
+        @NonNull
+        public VirtualCameraConfig build() {
+            return new VirtualCameraConfig(
+                    mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback);
+        }
+    }
+
+    private VirtualCameraConfig(
+            @NonNull String displayName,
+            @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
+            @NonNull Executor executor,
+            @NonNull VirtualCameraCallback callback) {
+        mDisplayName = requireNonNull(displayName, "Missing display name");
+        mStreamConfigurations =
+                Collections.unmodifiableSet(
+                        requireNonNull(streamConfigurations, "Missing stream configuration"));
+        if (mStreamConfigurations.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "At least one StreamConfiguration is needed to create a virtual camera.");
+        }
+        mCallback = requireNonNull(callback, "Missing callback");
+        mCallbackExecutor = requireNonNull(executor, "Missing callback executor");
+    }
+
+    /**
+     * @return The display name of this VirtualCamera
+     */
+    @NonNull
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
+     * Returns an unmodifiable set of the stream configurations added to this {@link
+     * VirtualCameraConfig}.
+     *
+     * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int)
+     */
+    @NonNull
+    public Set<VirtualCameraStreamConfig> getStreamConfigs() {
+        return mStreamConfigurations;
+    }
+
+    /** Returns the callback used to communicate from the server to the client. */
+    @NonNull
+    public VirtualCameraCallback getCallback() {
+        return mCallback;
+    }
+
+    /** Returns the executor onto which the callback should be run. */
+    @NonNull
+    public Executor getCallbackExecutor() {
+        return mCallbackExecutor;
+    }
+
+    /**
+     * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this
+     * {@link VirtualCameraConfig}
+     */
+    @NonNull
+    public VirtualCameraHalConfig getHalConfig() {
+        VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig();
+        halConfig.displayName = mDisplayName;
+        halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]);
+        return halConfig;
+    }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
new file mode 100644
index 0000000..7070a38
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
@@ -0,0 +1,12 @@
+package android.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+
+/**
+ * Configuration for VirtualCamera to be passed to the server and HAL service.
+ * @hide
+ */
+parcelable VirtualCameraHalConfig {
+  String displayName;
+  VirtualCameraStreamConfig[] streamConfigs;
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
new file mode 100644
index 0000000..c25d977
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
@@ -0,0 +1,30 @@
+/*
+ * 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.companion.virtual.camera;
+
+/***
+ * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing
+ * images from a {@link VirtualCamera}.
+ * @hide
+ */
+// TODO: b/289881985 - This is just a POC implementation for now, this will be extended
+// to a full featured Camera Session
+public interface VirtualCameraSession {
+
+    /** Close the session and release its resources. */
+    default void close() {}
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
new file mode 100644
index 0000000..da168de
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.graphics.ImageFormat;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}.
+ *
+ * @hide
+ */
+final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub {
+
+    @SuppressWarnings("FieldCanBeLocal")
+    // TODO: b/289881985: Will be used once connected with the CameraService
+    private final VirtualCameraSession mVirtualCameraSession;
+
+    VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) {
+        mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession);
+    }
+
+    @Override
+    public void configureStream(int width, int height, @ImageFormat.Format int format) {}
+
+    public void close() {}
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
new file mode 100644
index 0000000..304d455
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+/**
+ * A stream configuration supported by a virtual camera
+ * @hide
+ */
+parcelable VirtualCameraStreamConfig {
+    int width;
+    int height;
+    int format;
+}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ab669cc..b0ab11f 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -45,7 +45,8 @@
             ]
         },
         {
-            "name":"CarrierAppIntegrationTestCases"
+            "name":"CarrierAppIntegrationTestCases",
+            "keywords": ["internal"]
         },
         {
             "name":"CtsSilentUpdateHostTestCases"
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0c8eb02..b5efe58 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -13,3 +13,17 @@
     description: "Feature flag to enable the archiving feature."
     bug: "278553670"
 }
+
+flag {
+    name: "prevent_sdk_lib_app"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the prevent sdk-library be an application."
+    bug: "295843617"
+}
+
+flag {
+    name: "stay_stopped"
+    namespace: "backstage_power"
+    description: "Feature flag to improve stopped state enforcement"
+    bug: "296644915"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 07ff7be..ea0f049 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -6,3 +6,10 @@
     description: "Save guest and device policy global restrictions on the SYSTEM user's XML file."
     bug: "301067944"
 }
+
+flag {
+    name: "bind_wallpaper_service_on_its_own_thread_during_a_user_switch"
+    namespace: "multiuser"
+    description: "Bind wallpaper service on its own thread instead of system_server's main handler during a user switch."
+    bug: "302100344"
+}
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
index 71ad687..5b5ea37 100644
--- a/core/java/android/nfc/TEST_MAPPING
+++ b/core/java/android/nfc/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "NfcManagerTests"
+    },
+    {
+      "name": "CtsNfcTestCases"
     }
   ]
 }
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index ad3abd9..2d6e09a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -8,7 +8,6 @@
       "name": "FrameworksVibratorCoreTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
@@ -21,7 +20,6 @@
       "name": "FrameworksVibratorServicesTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
@@ -34,7 +32,6 @@
       "name": "CtsVibratorTestCases",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 80b7d40..4c8ef97 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2940,6 +2940,12 @@
      * Used to check if the context user is a restricted profile. Restricted profiles
      * may have a reduced number of available apps, app restrictions, and account restrictions.
      *
+     * <p>The caller must be in the same profile group as the context user or else hold
+     * <li>{@link android.Manifest.permission#MANAGE_USERS},
+     * <li>or {@link android.Manifest.permission#CREATE_USERS},
+     * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link android.Manifest.permission#QUERY_USERS}.
+     *
      * @return whether the context user is a restricted profile.
      * @hide
      */
@@ -2963,6 +2969,12 @@
      * Check if a user is a restricted profile. Restricted profiles may have a reduced number of
      * available apps, app restrictions, and account restrictions.
      *
+     * <p>Requires
+     * <li>{@link android.Manifest.permission#MANAGE_USERS},
+     * <li>or {@link android.Manifest.permission#CREATE_USERS},
+     * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link android.Manifest.permission#QUERY_USERS}.
+     *
      * @param user the user to check
      * @return whether the user is a restricted profile.
      * @hide
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index 7b8d52f..468c451 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "CtsNotificationTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
@@ -21,9 +18,6 @@
       "name": "FrameworksUiServicesTests",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
index 0fe974a..c9bd2ca 100644
--- a/core/java/android/text/TEST_MAPPING
+++ b/core/java/android/text/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsTextTestCases",
       "options": [
           {
-              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+              "exclude-annotation": "androidx.test.filters.FlakyTest"
           },
           {
               "exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
new file mode 100644
index 0000000..b0aa72a
--- /dev/null
+++ b/core/java/android/text/flags/fix_double_underline.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "fix_double_underline"
+  namespace: "text"
+  description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+  bug: "297336724"
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5eaded2..7674131 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1328,6 +1328,14 @@
     public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto";
 
     /**
+     * Hint indicating that the developer intends to fill this view with output from
+     * CredentialManager.
+     *
+     * @hide
+     */
+    public static final String AUTOFILL_HINT_CREDENTIAL_MANAGER = "credential";
+
+    /**
      * Hints for the autofill services that describes the content of the view.
      */
     private @Nullable String[] mAutofillHints;
@@ -32627,6 +32635,7 @@
      * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
      * @param enabled whether auto handwriting initiation is enabled for this view.
      * @attr ref android.R.styleable#View_autoHandwritingEnabled
+     * @see EditorInfo#setStylusHandwritingEnabled(boolean)
      */
     public void setAutoHandwritingEnabled(boolean enabled) {
         if (enabled) {
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 8ba8b8c..a07b62f 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,7 +16,11 @@
 
 package android.view.animation;
 
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
+import static android.view.flags.Flags.expectedPresentationTimeApi;
+
 import android.annotation.AnimRes;
+import android.annotation.FlaggedApi;
 import android.annotation.InterpolatorRes;
 import android.annotation.TestApi;
 import android.compat.annotation.ChangeId;
@@ -151,7 +155,12 @@
      * @return the expected presentation time of a frame in the
      *         {@link System#nanoTime()} time base.
      */
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
     public static long getExpectedPresentationTimeNanos() {
+        if (!expectedPresentationTimeApi()) {
+            return SystemClock.uptimeMillis();
+        }
+
         AnimationState state = sAnimationState.get();
         return state.mExpectedPresentationTimeNanos;
     }
@@ -164,6 +173,7 @@
      * @return the expected presentation time of a frame in the
      *         {@link SystemClock#uptimeMillis()} time base.
      */
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
     public static long getExpectedPresentationTimeMillis() {
         return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
     }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 4cb8788..6cf185a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1464,7 +1464,7 @@
         if (infos.size() == 0) {
             throw new IllegalArgumentException("No VirtualViewInfo found");
         }
-        if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) {
+        if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) {
             if (sDebug) {
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
                         + view.getAutofillId().toString());
@@ -1488,7 +1488,7 @@
      * @hide
      */
     public void notifyViewEnteredForFillDialog(View v) {
-        if (v.isCredential()
+        if (isCredmanRequested(v)
                 && mIsFillAndSaveDialogDisabledForCredentialManager) {
             if (sDebug) {
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
@@ -3434,6 +3434,22 @@
         }
     }
 
+    private boolean isCredmanRequested(View view) {
+        if (view.isCredential()) {
+            return true;
+        }
+        String[] hints = view.getAutofillHints();
+        if (hints == null) {
+            return false;
+        }
+        for (String hint : hints) {
+            if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Find a single view by its id.
      *
diff --git a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
index e3972ad..13a6f8d 100644
--- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
@@ -12,4 +12,11 @@
     namespace: "toolkit"
     description: "Feature flag for toolkit to set frame rate"
     bug: "293512962"
+}
+
+flag {
+    name: "expected_presentation_time_api"
+    namespace: "toolkit"
+    description: "Feature flag for using expected presentation time of the Choreographer"
+    bug: "278730197"
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 4e5cec7..a92420a 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -23,7 +23,9 @@
 import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME;
 import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
 import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
+import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -45,6 +47,7 @@
 import android.view.MotionEvent.ToolType;
 import android.view.View;
 import android.view.autofill.AutofillId;
+import android.widget.Editor;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -716,6 +719,33 @@
         return set;
     }
 
+    private boolean mIsStylusHandwritingEnabled;
+
+    /**
+     * Set {@code true} if the {@link Editor} has
+     * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
+     * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+     * it supports stylus handwriting.
+     *
+     * @param enabled {@code true} if stylus handwriting is enabled.
+     * @see View#setAutoHandwritingEnabled(boolean)
+     */
+    @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED)
+    public void setStylusHandwritingEnabled(boolean enabled) {
+        mIsStylusHandwritingEnabled = enabled;
+    }
+
+    /**
+     * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+     * {@code false} by default.
+     * @see #setStylusHandwritingEnabled(boolean)
+     * @see InputMethodManager#isStylusHandwritingAvailable()
+     */
+    @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED)
+    public boolean isStylusHandwritingEnabled() {
+        return mIsStylusHandwritingEnabled;
+    }
+
     /**
      * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
      * matter what user ID the calling process has.
@@ -1211,6 +1241,7 @@
         pw.println(prefix + "supportedHandwritingGesturePreviewTypes="
                 + InputMethodDebug.handwritingGestureTypeFlagsToString(
                         mSupportedHandwritingGesturePreviewTypes));
+        pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled);
         pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
         if (targetInputMethodUser != null) {
             pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1277,6 +1308,9 @@
         dest.writeBundle(extras);
         dest.writeInt(mSupportedHandwritingGestureTypes);
         dest.writeInt(mSupportedHandwritingGesturePreviewTypes);
+        if (Flags.editorinfoHandwritingEnabled()) {
+            dest.writeBoolean(mIsStylusHandwritingEnabled);
+        }
         dest.writeBoolean(mInitialSurroundingText != null);
         if (mInitialSurroundingText != null) {
             mInitialSurroundingText.writeToParcel(dest, flags);
@@ -1316,6 +1350,9 @@
                     res.extras = source.readBundle();
                     res.mSupportedHandwritingGestureTypes = source.readInt();
                     res.mSupportedHandwritingGesturePreviewTypes = source.readInt();
+                    if (Flags.editorinfoHandwritingEnabled()) {
+                        res.mIsStylusHandwritingEnabled = source.readBoolean();
+                    }
                     boolean hasInitialSurroundingText = source.readBoolean();
                     if (hasInitialSurroundingText) {
                         res.mInitialSurroundingText =
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c144289..c14b510 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -6,4 +6,12 @@
     description: "Feature flag for refactoring InsetsController and removing ImeInsetsSourceConsumer"
     bug: "298172246"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "editorinfo_handwriting_enabled"
+    namespace: "input_method"
+    description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled"
+    bug: "293898187"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index d25c8a8..7b7e341 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -25,9 +25,12 @@
 interface ITaskFragmentOrganizerController {
 
     /**
-     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system
+     * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional
+     * system capabilities.
      */
-    void registerOrganizer(in ITaskFragmentOrganizer organizer);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)")
+    void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer);
 
     /**
      * Unregisters a previously registered TaskFragmentOrganizer.
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index f785a3d..a6c9cec 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -22,9 +22,11 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -32,6 +34,8 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -140,12 +144,34 @@
     }
 
     /**
-     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
      */
     @CallSuper
     public void registerOrganizer() {
+        // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed.
         try {
-            getController().registerOrganizer(mInterface);
+            getController().registerOrganizer(mInterface, false /* isSystemOrganizer */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
+     *
+     * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer
+     * will have additional system capabilities, including: (1) it will receive SurfaceControl for
+     * the organized TaskFragment, and (2) it needs to update the
+     * {@link android.view.SurfaceControl} following the window change accordingly.
+     *
+     * @hide
+     */
+    @CallSuper
+    @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true)
+    @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+    public void registerOrganizer(boolean isSystemOrganizer) {
+        try {
+            getController().registerOrganizer(mInterface, isSystemOrganizer);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 3c5d60d..4dada10 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -30,6 +30,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -192,6 +193,9 @@
         @Nullable
         private TaskFragmentParentInfo mTaskFragmentParentInfo;
 
+        @Nullable
+        private SurfaceControl mSurfaceControl;
+
         public Change(@ChangeType int type) {
             mType = type;
         }
@@ -206,6 +210,7 @@
             mActivityIntent = in.readTypedObject(Intent.CREATOR);
             mActivityToken = in.readStrongBinder();
             mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
+            mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR);
         }
 
         @Override
@@ -219,6 +224,7 @@
             dest.writeTypedObject(mActivityIntent, flags);
             dest.writeStrongBinder(mActivityToken);
             dest.writeTypedObject(mTaskFragmentParentInfo, flags);
+            dest.writeTypedObject(mSurfaceControl, flags);
         }
 
         /** The change is related to the TaskFragment created with this unique token. */
@@ -306,6 +312,13 @@
             return this;
         }
 
+        /** @hide */
+        @NonNull
+        public Change setTaskFragmentSurfaceControl(@Nullable SurfaceControl sc) {
+            mSurfaceControl = sc;
+            return this;
+        }
+
         @ChangeType
         public int getType() {
             return mType;
@@ -359,6 +372,21 @@
             return mTaskFragmentParentInfo;
         }
 
+        /**
+         * Gets the {@link SurfaceControl} of the TaskFragment. This field is {@code null} for
+         * a regular {@link TaskFragmentOrganizer} and is only available for a system
+         * {@link TaskFragmentOrganizer} in the
+         * {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_APPEARED} event. See
+         * {@link ITaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer,
+         * boolean)}
+         *
+         * @hide
+         */
+        @Nullable
+        public SurfaceControl getTaskFragmentSurfaceControl() {
+            return mSurfaceControl;
+        }
+
         @Override
         public String toString() {
             return "Change{ type=" + mType + " }";
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 663067c..0b69030 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -48,7 +48,6 @@
     static final String TAG = "PackageMonitor";
 
     final IntentFilter mPackageFilt;
-    final IntentFilter mNonDataFilt;
 
     Context mRegisteredContext;
     Handler mRegisteredHandler;
@@ -78,13 +77,6 @@
         if (isCore) {
             mPackageFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-
-        mNonDataFilt = new IntentFilter();
-        // UserController sends the broadcast
-        mNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
-        if (isCore) {
-            mNonDataFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        }
     }
 
     @UnsupportedAppUsage
@@ -111,10 +103,8 @@
         mRegisteredHandler = Objects.requireNonNull(handler);
         if (user != null) {
             context.registerReceiverAsUser(this, user, mPackageFilt, null, mRegisteredHandler);
-            context.registerReceiverAsUser(this, user, mNonDataFilt, null, mRegisteredHandler);
         } else {
             context.registerReceiver(this, mPackageFilt, null, mRegisteredHandler);
-            context.registerReceiver(this, mNonDataFilt, null, mRegisteredHandler);
         }
         if (mPackageMonitorCallback == null) {
             PackageManager pm = mRegisteredContext.getPackageManager();
@@ -216,9 +206,6 @@
         return false;
     }
 
-    public void onHandleUserStop(Intent intent, int userHandle) {
-    }
-
     public void onUidRemoved(int uid) {
     }
     
@@ -463,10 +450,6 @@
                     intent.getIntExtra(Intent.EXTRA_UID, 0), true);
         } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
             onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
-        } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-            if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
-                onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-            }
         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
             String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
             mAppearingPackages = pkgList;
diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
new file mode 100644
index 0000000..4e3888a
--- /dev/null
+++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.internal.foldables;
+
+import android.content.res.Resources;
+import android.sysprop.FoldLockBehaviorProperties;
+
+import com.android.internal.R;
+
+/**
+ * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing
+ */
+public class FoldLockSettingAvailabilityProvider {
+
+    boolean mFoldLockBehaviorResourceValue;
+
+    public FoldLockSettingAvailabilityProvider(Resources resources) {
+        mFoldLockBehaviorResourceValue = resources.getBoolean(
+                R.bool.config_fold_lock_behavior);
+    }
+
+    public boolean isFoldLockBehaviorAvailable() {
+        return mFoldLockBehaviorResourceValue
+                && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false);
+    }
+}
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index ddfd0ed..c09181f 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsRoleTestCases",
       "options": [
           {
-              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+              "exclude-annotation": "androidx.test.filters.FlakyTest"
           }
       ]
     },
@@ -15,7 +15,7 @@
             "include-filter": "android.permission.cts.PermissionControllerTest"
           },
           {
-            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
           }
       ]
     },
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7eeac29..4d8eeac 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -316,7 +316,14 @@
      */
     void requestTileServiceListeningState(in ComponentName componentName);
 
-    void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
+    void requestAddTile(
+        int callingUid,
+        in ComponentName componentName,
+        in CharSequence appName,
+        in CharSequence label,
+        in Icon icon,
+        in IAddTileResultCallback callback
+    );
     void cancelRequestAddTile(in String packageName);
 
     /** Notifies System UI about an update to the media tap-to-transfer sender state. */
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 878e6b3..71d696e 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -172,28 +172,17 @@
     <integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer>
     <java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" />
 
-    <!-- Telephony config for services supported by satellite providers. The format of each config
-         string in the array is as follows: "PLMN_1:service_1,service_2,..."
-         where PLMN is the satellite PLMN of a provider and service is an integer with the
-         following value:
-            1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
-            2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
-            3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
-            4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
-            5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
-            6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}
-         Example of a config string: "10011:2,3"
+    <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
+         to identify providers that should be ignored if the carrier config
+         carrier_supported_satellite_services_per_provider_bundle does not support them.
+         -->
+    <string-array name="config_satellite_providers" translatable="false"></string-array>
+    <java-symbol type="array" name="config_satellite_providers" />
 
-         The PLMNs not configured in this array will be ignored and will not be used for satellite
-         scanning. -->
-    <string-array name="config_satellite_services_supported_by_providers" translatable="false">
-    </string-array>
-    <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
-
-    <!-- The identifier of the satellite's eSIM profile preloaded on the device. The identifier is
-    composed of MCC and MNC of the satellite PLMN with the format "mccmnc". -->
-    <string name="config_satellite_esim_identifier" translatable="false"></string>
-    <java-symbol type="string" name="config_satellite_esim_identifier" />
+    <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
+         of the satellite PLMN with the format "mccmnc". -->
+    <string name="config_satellite_sim_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_identifier" />
 
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
diff --git a/core/sysprop/Android.bp b/core/sysprop/Android.bp
index f89099e..512a2eb 100644
--- a/core/sysprop/Android.bp
+++ b/core/sysprop/Android.bp
@@ -36,3 +36,10 @@
     api_packages: ["android.sysprop"],
     vendor_available: false,
 }
+
+sysprop_library {
+    name: "com.android.sysprop.foldlockbehavior",
+    srcs: ["FoldLockBehaviorProperties.sysprop"],
+    property_owner: "Platform",
+    api_packages: ["android.sysprop"],
+}
diff --git a/core/sysprop/FoldLockBehaviorProperties.sysprop b/core/sysprop/FoldLockBehaviorProperties.sysprop
new file mode 100644
index 0000000..d337954
--- /dev/null
+++ b/core/sysprop/FoldLockBehaviorProperties.sysprop
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: "android.sysprop.FoldLockBehaviorProperties"
+owner: Platform
+
+prop {
+    api_name: "fold_lock_setting_enabled"
+    type: Boolean
+    prop_name: "persist.fold_lock_setting_enabled"
+    scope: Internal
+    access: Readonly
+}
diff --git a/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt b/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.foldlockbehavior-current.txt
diff --git a/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt b/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt
new file mode 100644
index 0000000..1327470
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.foldlockbehavior-latest.txt
@@ -0,0 +1,9 @@
+props {
+  module: "android.sysprop.FoldLockBehaviorProperties"
+  prop {
+    api_name: "fold_lock_setting_enabled"
+    type: Boolean
+    prop_name: "persist.fold_lock_setting_enabled"
+    scope: Internal
+  }
+}
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 5d92296..49ed3a8 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,13 +18,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.IUriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.RecordingCanvas;
 import android.graphics.Region;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Parcel;
+import android.os.RemoteException;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -34,6 +41,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -457,6 +465,81 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
+    @SmallTest
+    public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
+        int uid = 12345;
+        String packageName = "test_pkg";
+
+        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+                .getBitmap();
+        final File dir = getContext().getExternalFilesDir(null);
+        final File file1 = new File(dir, "file1-original.png");
+        bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+        final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+        TestableIUriGrantsManager ugm =
+                new TestableIUriGrantsManager(/* rejectCheckRequests */ false);
+
+        Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+                getContext(), ugm, uid, packageName);
+        assertThat(loadedDrawable).isNotNull();
+
+        assertThat(ugm.mRequests.size()).isEqualTo(1);
+        TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+        assertThat(r.mCallingUid).isEqualTo(uid);
+        assertThat(r.mPackageName).isEqualTo(packageName);
+        assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+        assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+        final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
+                loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
+                loadedDrawable.getIntrinsicHeight());
+        loadedDrawable.draw(new Canvas(test1));
+
+        bit1.compress(Bitmap.CompressFormat.PNG, 100,
+                new FileOutputStream(new File(dir, "bitmap1-original.png")));
+        test1.compress(Bitmap.CompressFormat.PNG, 100,
+                new FileOutputStream(new File(dir, "bitmap1-test.png")));
+        if (!equalBitmaps(bit1, test1)) {
+            findBitmapDifferences(bit1, test1);
+            fail("bitmap1 differs, check " + dir);
+        }
+    }
+
+    @SmallTest
+    public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
+        int uid = 12345;
+        String packageName = "test_pkg";
+
+        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+                .getBitmap();
+        final File dir = getContext().getExternalFilesDir(null);
+        final File file1 = new File(dir, "file1-original.png");
+        bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+        final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+        TestableIUriGrantsManager ugm =
+                new TestableIUriGrantsManager(/* rejectCheckRequests */ true);
+
+        Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+                getContext(), ugm, uid, packageName);
+
+        assertThat(ugm.mRequests.size()).isEqualTo(1);
+        TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+        assertThat(r.mCallingUid).isEqualTo(uid);
+        assertThat(r.mPackageName).isEqualTo(packageName);
+        assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+        assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+        assertThat(loadedDrawable).isNull();
+    }
+
+
     // ======== utils ========
 
     static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
@@ -541,4 +624,77 @@
         }
         L(sb.toString());
     }
-}
+
+    private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {
+
+        final ArrayList<CheckRequest> mRequests = new ArrayList<>();
+        final boolean mRejectCheckRequests;
+
+        TestableIUriGrantsManager(boolean rejectCheckRequests) {
+            this.mRejectCheckRequests = rejectCheckRequests;
+        }
+
+        @Override
+        public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
+                int i2, int i3) throws RemoteException {
+
+        }
+
+        @Override
+        public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public void clearGrantedUriPermissions(String s, int i) throws RemoteException {
+
+        }
+
+        @Override
+        public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
+                throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public int checkGrantUriPermission_ignoreNonSystem(
+                int uid, String packageName, Uri uri, int mode, int userId)
+                throws RemoteException {
+            CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
+            mRequests.add(r);
+            if (mRejectCheckRequests) {
+                throw new SecurityException();
+            } else {
+                return uid;
+            }
+        }
+
+        static class CheckRequest {
+            final int mCallingUid;
+            final String mPackageName;
+            final Uri mUri;
+            final int mMode;
+            final int mUserId;
+
+            CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
+                this.mCallingUid = callingUid;
+                this.mPackageName = packageName;
+                this.mUri = uri;
+                this.mMode = mode;
+                this.mUserId = userId;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 6e73b9f..4839dd2 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -503,6 +503,7 @@
                 + "prefix: hintLocales=null\n"
                 + "prefix: supportedHandwritingGestureTypes=(none)\n"
                 + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+                + "prefix: isStylusHandwritingEnabled=false\n"
                 + "prefix: contentMimeTypes=null\n");
     }
 
@@ -521,6 +522,9 @@
         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
         info.setSupportedHandwritingGesturePreviews(
                 Stream.of(SelectGesture.class).collect(Collectors.toSet()));
+        if (Flags.editorinfoHandwritingEnabled()) {
+            info.setStylusHandwritingEnabled(true);
+        }
         info.packageName = "android.view.inputmethod";
         info.autofillId = new AutofillId(123);
         info.fieldId = 456;
@@ -544,6 +548,8 @@
                         + "prefix2: hintLocales=[en,es,zh]\n"
                         + "prefix2: supportedHandwritingGestureTypes=SELECT\n"
                         + "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
+                        + "prefix2: isStylusHandwritingEnabled="
+                                + Flags.editorinfoHandwritingEnabled() + "\n"
                         + "prefix2: contentMimeTypes=[image/png]\n"
                         + "prefix2: targetInputMethodUserId=10\n");
     }
@@ -565,6 +571,7 @@
                         + "prefix: hintLocales=null\n"
                         + "prefix: supportedHandwritingGestureTypes=(none)\n"
                         + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+                        + "prefix: isStylusHandwritingEnabled=false\n"
                         + "prefix: contentMimeTypes=null\n");
     }
 
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a8b4032..a5bbeb5 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -17,6 +17,7 @@
 package android.window.flags;
 
 import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
 
 import android.platform.test.annotations.Presubmit;
 
@@ -42,4 +43,10 @@
         // No crash when accessing the flag.
         syncWindowConfigUpdateFlag();
     }
+
+    @Test
+    public void testTaskFragmentSystemOrganizerFlag() {
+        // No crash when accessing the flag.
+        taskFragmentSystemOrganizerFlag();
+    }
 }
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index c7eddabe..a339907 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.content;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -67,7 +68,7 @@
 
         spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
         assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
-        verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+        verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
                 eq(null), eq(mMockHandler));
 
         assertThrows(IllegalStateException.class,
@@ -138,19 +139,6 @@
     }
 
     @Test
-    public void testPackageMonitorDoHandlePackageEventUserStop() throws Exception {
-        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
-
-        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
-        spyPackageMonitor.doHandlePackageEvent(intent);
-
-        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
-        verify(spyPackageMonitor, times(1)).onHandleUserStop(eq(intent), eq(FAKE_USER_ID));
-        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
-    }
-
-    @Test
     public void testPackageMonitorDoHandlePackageEventExternalApplicationAvailable()
             throws Exception {
         PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
index 2f3afa6..54a5ff1 100644
--- a/core/tests/vibrator/TEST_MAPPING
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "FrameworksVibratorCoreTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 708feeb..5509f00 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -24,9 +24,12 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.IUriGrantsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.RequiresPermission;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -530,6 +536,46 @@
         return loadDrawable(context);
     }
 
+    /**
+     * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
+     * to load the resource. The check will be performed using the permissions of the passed uid,
+     * and not those of the caller.
+     * <p>
+     * This should be called for {@link Icon} objects that come from a not trusted source and may
+     * contain a URI.
+     *
+     * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
+     * return {@code null}.
+     *
+     * @see #loadDrawable
+     *
+     * @hide
+     */
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    public Drawable loadDrawableCheckingUriGrant(
+            Context context,
+            IUriGrantsManager iugm,
+            int callingUid,
+            String packageName
+    ) {
+        if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
+            try {
+                iugm.checkGrantUriPermission_ignoreNonSystem(
+                        callingUid,
+                        packageName,
+                        ContentProvider.getUriWithoutUserId(getUri()),
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                        ContentProvider.getUserIdFromUri(getUri())
+                );
+            } catch (SecurityException | RemoteException e) {
+                Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
+                return null;
+            }
+        }
+        return loadDrawable(context);
+    }
+
     /** @hide */
     public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
 
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7faf380..705e387 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -435,10 +435,10 @@
     <dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
 
     <!-- The height of the handle menu's "More Actions" pill in desktop mode, but not freeform. -->
-    <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
+    <dimen name="desktop_mode_handle_menu_more_actions_pill_height">104dp</dimen>
 
     <!-- The height of the handle menu's "More Actions" pill in freeform desktop windowing mode. -->
-    <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">104dp</dimen>
+    <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">52dp</dimen>
 
     <!-- The top margin of the handle menu in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
index cc37bd3a4..d45e126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -112,7 +112,7 @@
                 viewPositionOnTouchDown.set(v.translationX, v.translationY)
 
                 performedLongClick = false
-                v.handler.postDelayed({
+                v.handler?.postDelayed({
                     if (v.isLongClickable) {
                         performedLongClick = v.performLongClick()
                     }
@@ -122,7 +122,7 @@
             MotionEvent.ACTION_MOVE -> {
                 if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
                     movedEnough = true
-                    v.handler.removeCallbacksAndMessages(null)
+                    v.handler?.removeCallbacksAndMessages(null)
                 }
 
                 if (movedEnough) {
@@ -138,7 +138,7 @@
                 } else if (!performedLongClick) {
                     v.performClick()
                 } else {
-                    v.handler.removeCallbacksAndMessages(null)
+                    v.handler?.removeCallbacksAndMessages(null)
                 }
 
                 velocityTracker.clear()
@@ -146,7 +146,7 @@
             }
 
             MotionEvent.ACTION_CANCEL -> {
-                v.handler.removeCallbacksAndMessages(null)
+                v.handler?.removeCallbacksAndMessages(null)
                 velocityTracker.clear()
                 movedEnough = false
             }
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 3d825f0..4ea14f4 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
@@ -720,6 +720,8 @@
         mSplitLayout.setDivideRatio(snapPosition);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
+        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                false /* reparentLeafTaskIfRelaunch */);
         setRootForceTranslucent(false, wct);
 
         // Make sure the launch options will put tasks in the corresponding split roots
@@ -767,6 +769,8 @@
         mSplitLayout.setDivideRatio(snapPosition);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
+        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                false /* reparentLeafTaskIfRelaunch */);
         setRootForceTranslucent(false, wct);
 
         options1 = options1 != null ? options1 : new Bundle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index de03f58..e0635ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -322,6 +322,7 @@
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
             mAnimations.remove(transition);
+            info.releaseAllSurfaces();
             finishCallback.onTransitionFinished(null /* wct */);
         };
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index f82b212..a7a11de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -163,6 +163,9 @@
             final ImageButton splitscreenBtn = windowingPillView.findViewById(
                     R.id.split_screen_button);
             final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+            // TODO: Remove once implemented.
+            floatingBtn.setVisibility(View.GONE);
+
             final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
             fullscreenBtn.setOnClickListener(mOnClickListener);
             splitscreenBtn.setOnClickListener(mOnClickListener);
@@ -196,6 +199,9 @@
         }
         final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
         selectBtn.setOnClickListener(mOnClickListener);
+        final Button screenshotBtn = moreActionsPillView.findViewById(R.id.screenshot_button);
+        // TODO: Remove once implemented.
+        screenshotBtn.setVisibility(View.GONE);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
new file mode 100644
index 0000000..2cd08a4a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.wm.shell.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.NetflixAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Netflix app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ *
+ * Actions:
+ * ```
+ *     Launch Netflix and start playing a video
+ *     Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited from [PipTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+    override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+
+    override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+        setup {
+            standardAppHelper.launchViaIntent(
+                wmHelper,
+                NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+                ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
+                    NetflixAppHelper.WATCH_ACTIVITY)
+            )
+            standardAppHelper.waitForVideoPlaying()
+        }
+    }
+
+    override val defaultTeardown: FlickerBuilder.() -> Unit = {
+        teardown {
+            standardAppHelper.exit(wmHelper)
+        }
+    }
+
+    override val thisTransition: FlickerBuilder.() -> Unit = {
+        transitions {
+            tapl.goHomeFromImmersiveFullscreenApp()
+        }
+    }
+
+    @Postsubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // Netflix uses source rect hint, so PiP overlay is never present
+    }
+
+    @Postsubmit
+    @Test
+    override fun focusChanges() {
+        // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.focusChanges()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_0),
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index b0e847f..e37d806 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -19,15 +19,10 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
 import org.junit.Test
 
 open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index f847e40..2a50912 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -19,15 +19,10 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
 import org.junit.Test
 
 open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 31e52e2..d5da1a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
 import org.junit.Test
 
 open class DismissSplitScreenByDividerGesturalNavLandscape :
     DismissSplitScreenByDivider(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 0870073..7fdcb9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
 import org.junit.Test
 
 open class DismissSplitScreenByDividerGesturalNavPortrait :
     DismissSplitScreenByDivider(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 1bb6c45..308e954 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
 import org.junit.Test
 
 open class DismissSplitScreenByGoHomeGesturalNavLandscape :
     DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index bb084d6..39e75bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
 import org.junit.Test
 
 open class DismissSplitScreenByGoHomeGesturalNavPortrait :
     DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index bc5857f..e18da17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -19,15 +19,10 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
 import org.junit.Test
 
 open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index e4faa4a..00d60e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -19,15 +19,10 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
 import org.junit.Test
 
 open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 7431974..d7efbc8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index c2b0609..4eece3f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 7d1072d..d96b056 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 79760d1..809b690 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index ff3729a..bbdf2d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index ffbcfe4..5c29fd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 5e937c0..a7398eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index 89db8a0..eae88ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index bf4ee7a..7e8ee04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenFromOverviewGesturalNavLandscape :
     EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 7e96f06..9295c33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
 import org.junit.Test
 
 open class EnterSplitScreenFromOverviewGesturalNavPortrait :
     EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index a84deac..4b59e9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index afcc743..5ff36d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 2f96f5f..c0cb721 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index efbeb76..8c14088 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 232fccc..7b6614b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromHomeGesturalNavLandscape :
     SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index d7f2845..5df5be9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromHomeGesturalNavPortrait :
     SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 019c946..9d63003 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromRecentGesturalNavLandscape :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 5678861..9fa04b2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBackToSplitFromRecentGesturalNavPortrait :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index b3c4ea2..9386aa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBetweenSplitPairsGesturalNavLandscape :
     SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index f88180f..5ef2167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
 import org.junit.Test
 
 open class SwitchBetweenSplitPairsGesturalNavPortrait :
     SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 391cb9b7..9caab9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -18,18 +18,13 @@
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.BlockJUnit4ClassRunner
 
 @RunWith(BlockJUnit4ClassRunner::class)
 open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index 4af133d..bf484e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -18,18 +18,13 @@
 
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.BlockJUnit4ClassRunner
 
 @RunWith(BlockJUnit4ClassRunner::class)
 open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
-    @get:Rule
-    val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2d93047..02c9d30 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -35,7 +35,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 270dbc4..83d9f65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -39,7 +39,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
similarity index 91%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index 0dc16f4..cb29a21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.wm.shell.bubbles;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -27,10 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.bubbles.BubbleOverflow;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.TestableBubblePositioner;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
index 9655f97..f8eb50b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -38,7 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f2c29f4..dea1617 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -43,7 +43,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRemoteTransition
+import com.android.wm.shell.transition.TestRemoteTransition
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 69f664a..499e339 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -37,8 +37,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.TransitionInfoBuilder;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index d542139..ebc284b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -62,9 +62,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRemoteTransition;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.TransitionInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -75,6 +73,8 @@
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.TestRemoteTransition;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index a57a7bf..d598678 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -94,7 +94,6 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.TransitionInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
index 0df42b3..39ab238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
 
 import android.os.IBinder;
 import android.os.RemoteException;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
similarity index 98%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index a658375..8343858 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e1dd145..ff1eedb 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -143,6 +143,7 @@
                 "libcrypto",
                 "libsync",
                 "libui",
+                "aconfig_text_flags_c_lib",
             ],
             static_libs: [
                 "libEGL_blobCache",
@@ -712,11 +713,13 @@
     ],
 
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libhwui_static",
     ],
     shared_libs: [
         "libmemunreachable",
+        "server_configurable_flags",
     ],
     srcs: [
         "tests/unit/main.cpp",
@@ -756,6 +759,7 @@
         "tests/unit/TestUtilsTests.cpp",
         "tests/unit/ThreadBaseTests.cpp",
         "tests/unit/TypefaceTests.cpp",
+        "tests/unit/UnderlineTest.cpp",
         "tests/unit/VectorDrawableTests.cpp",
         "tests/unit/WebViewFunctorManagerTests.cpp",
     ],
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 0000000..ffb329d
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif  // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+    return com_android_text_flags_fix_double_underline();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
+}  // namespace text_feature
+
+}  // namespace android
+
+#endif  // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8394c3c..31fc929 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
 #include <utility>
 
 #include "CanvasProperty.h"
+#include "FeatureFlags.h"
 #include "Mesh.h"
 #include "NinePatchUtils.h"
 #include "VectorDrawable.h"
@@ -795,7 +796,9 @@
     sk_sp<SkTextBlob> textBlob(builder.make());
 
     applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
-    drawTextDecorations(x, y, totalAdvance, paintCopy);
+    if (!text_feature::fix_double_underline()) {
+        drawTextDecorations(x, y, totalAdvance, paintCopy);
+    }
 }
 
 void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2351797..80b6c03 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
 
 #include "Canvas.h"
 
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
 #include "MinikinUtils.h"
 #include "Paint.h"
 #include "Properties.h"
 #include "RenderNode.h"
 #include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
 #include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
 
 namespace android {
 
@@ -34,13 +35,6 @@
     return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
 }
 
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
-                              const Paint& paint, Canvas* canvas) {
-    const SkScalar strokeWidth = fmax(thickness, 1.0f);
-    const SkScalar bottom = top + strokeWidth;
-    canvas->drawRect(left, top, right, bottom, paint);
-}
-
 void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
     // paint has already been filtered by our caller, so we can ignore any filter
     const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@
     }
 }
 
-static void simplifyPaint(int color, Paint* paint) {
-    paint->setColor(color);
-    paint->setShader(nullptr);
-    paint->setColorFilter(nullptr);
-    paint->setLooper(nullptr);
-    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
-    paint->setStrokeJoin(SkPaint::kRound_Join);
-    paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
-    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
-                    float y, float totalAdvance)
-            : layout(layout)
-            , canvas(canvas)
-            , paint(paint)
-            , x(x)
-            , y(y)
-            , totalAdvance(totalAdvance) {}
-
-    void operator()(size_t start, size_t end) {
-        auto glyphFunc = [&](uint16_t* text, float* positions) {
-            for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
-                text[textIndex++] = layout.getGlyphId(i);
-                positions[posIndex++] = x + layout.getX(i);
-                positions[posIndex++] = y + layout.getY(i);
-            }
-        };
-
-        size_t glyphCount = end - start;
-
-        if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
-            // high contrast draw path
-            int color = paint.getColor();
-            int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
-            bool darken = channelSum < (128 * 3);
-
-            // outline
-            gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
-            Paint outlinePaint(paint);
-            simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
-            outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
-            canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
-            // inner
-            gDrawTextBlobMode = DrawTextBlobMode::HctInner;
-            Paint innerPaint(paint);
-            simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
-            innerPaint.setStyle(SkPaint::kFill_Style);
-            canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
-            gDrawTextBlobMode = DrawTextBlobMode::Normal;
-        } else {
-            // standard draw path
-            canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
-        }
-    }
-
-private:
-    const minikin::Layout& layout;
-    Canvas* canvas;
-    const Paint& paint;
-    float x;
-    float y;
-    float totalAdvance;
-};
-
 void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
                         int glyphCount, const Paint& paint) {
     // Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -182,6 +109,31 @@
 
     DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
+
+    if (text_feature::fix_double_underline()) {
+        Paint copied(paint);
+        PaintFilter* filter = getPaintFilter();
+        if (filter != nullptr) {
+            filter->filterFullPaint(&copied);
+        }
+        const bool isUnderline = copied.isUnderline();
+        const bool isStrikeThru = copied.isStrikeThru();
+        if (isUnderline || isStrikeThru) {
+            const SkScalar left = x;
+            const SkScalar right = x + layout.getAdvance();
+            if (isUnderline) {
+                const SkScalar top = y + f.getUnderlinePosition();
+                drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+            }
+            if (isStrikeThru) {
+                float textSize = paint.getSkFont().getSize();
+                const float position = textSize * Paint::kStdStrikeThru_Top;
+                const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+                const SkScalar top = y + position;
+                drawStroke(left, right, top, thickness, copied, this);
+            }
+        }
+    }
 }
 
 void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d..9ec023b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@
      * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
      */
     virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
-                            float y,float totalAdvance) = 0;
+                            float y, float totalAdvance) = 0;
     virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 0000000..2e6e976
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+                              const Paint& paint, Canvas* canvas) {
+    const SkScalar strokeWidth = fmax(thickness, 1.0f);
+    const SkScalar bottom = top + strokeWidth;
+    canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+    paint->setColor(color);
+    paint->setShader(nullptr);
+    paint->setColorFilter(nullptr);
+    paint->setLooper(nullptr);
+    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+    paint->setStrokeJoin(SkPaint::kRound_Join);
+    paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+                    float y, float totalAdvance)
+            : layout(layout)
+            , canvas(canvas)
+            , paint(paint)
+            , x(x)
+            , y(y)
+            , totalAdvance(totalAdvance)
+            , underlinePosition(0)
+            , underlineThickness(0) {}
+
+    void operator()(size_t start, size_t end) {
+        auto glyphFunc = [&](uint16_t* text, float* positions) {
+            for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+                text[textIndex++] = layout.getGlyphId(i);
+                positions[posIndex++] = x + layout.getX(i);
+                positions[posIndex++] = y + layout.getY(i);
+            }
+        };
+
+        size_t glyphCount = end - start;
+
+        if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+            // high contrast draw path
+            int color = paint.getColor();
+            int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+            bool darken = channelSum < (128 * 3);
+
+            // outline
+            gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+            Paint outlinePaint(paint);
+            simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+            outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+            canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+            // inner
+            gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+            Paint innerPaint(paint);
+            simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+            innerPaint.setStyle(SkPaint::kFill_Style);
+            canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+            gDrawTextBlobMode = DrawTextBlobMode::Normal;
+        } else {
+            // standard draw path
+            canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+        }
+
+        if (text_feature::fix_double_underline()) {
+            // Extract underline position and thickness.
+            if (paint.isUnderline()) {
+                SkFontMetrics metrics;
+                paint.getSkFont().getMetrics(&metrics);
+                const float textSize = paint.getSkFont().getSize();
+                SkScalar position;
+                if (!metrics.hasUnderlinePosition(&position)) {
+                    position = textSize * Paint::kStdUnderline_Top;
+                }
+                SkScalar thickness;
+                if (!metrics.hasUnderlineThickness(&thickness)) {
+                    thickness = textSize * Paint::kStdUnderline_Thickness;
+                }
+
+                // If multiple fonts are used, use the most bottom position and most thick stroke
+                // width as the underline position. This follows the CSS standard:
+                // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+                // <quote>
+                // The exact position and thickness of line decorations is UA-defined in this level.
+                // However, for underlines and overlines the UA must use a single thickness and
+                // position on each line for the decorations deriving from a single decorating box.
+                // </quote>
+                underlinePosition = std::max(underlinePosition, position);
+                underlineThickness = std::max(underlineThickness, thickness);
+            }
+        }
+    }
+
+    float getUnderlinePosition() const { return underlinePosition; }
+    float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+    const minikin::Layout& layout;
+    Canvas* canvas;
+    const Paint& paint;
+    float x;
+    float y;
+    float totalAdvance;
+    float underlinePosition;
+    float underlineThickness;
+};
+
+}  // namespace android
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7..0fffee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@
     p.writeFloat(info.fDisplayRatioHdr);
     // base image type
     p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
-    // type
-    p.writeInt32(static_cast<int32_t>(info.fType));
 #else
     doThrowRE(env, "Cannot use parcels outside of Android!");
 #endif
@@ -232,7 +230,6 @@
     info.fDisplayRatioSdr = p.readFloat();
     info.fDisplayRatioHdr = p.readFloat();
     info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
-    info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
 
     fromJava(nativeObject)->info = info;
 #else
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 0000000..db2be20
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+    void* p = const_cast<void*>(ptr);
+    size_t len = reinterpret_cast<size_t>(context);
+    munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+    int fd = open(fileName, O_RDONLY);
+    LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+    struct stat st = {};
+    LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+    void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+    sk_sp<SkData> skData =
+            SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+    LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+    std::shared_ptr<minikin::MinikinFont> font =
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+                                              std::vector<minikin::FontVariation>());
+    std::vector<std::shared_ptr<minikin::Font>> fonts;
+    fonts.push_back(minikin::Font::Builder(font).build());
+    return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+    return Typeface::createFromFamilies(
+            std::vector<std::shared_ptr<minikin::FontFamily>>(
+                    {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+            RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+    return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+                                  0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+    // Create canvas
+    SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+    sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+    SkBitmap skBitmap;
+    bitmap->getSkBitmap(&skBitmap);
+    SkiaCanvas canvas(skBitmap);
+
+    // Create minikin::Layout
+    std::unique_ptr<Typeface> typeface(makeTypeface());
+    minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+    DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+    MinikinUtils::forFontRun(layout, paint, f);
+    return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // the text is "abc"
+    DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+    EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // The text is あいう in Japanese
+    DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+    EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // The text is aいc. The only middle of the character is Japanese.
+    DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+    // We use the bottom, thicker line as underline. Here, use Noto's one.
+    EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+}  // namespace
diff --git a/location/Android.bp b/location/Android.bp
index cfe0e49..eb7cd01 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -39,3 +39,8 @@
         ],
     },
 }
+
+platform_compat_config {
+    name: "framework-location-compat-config",
+    src: ":framework-location",
+}
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index a792498..7aa9118 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "MediaProjectionTests",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index a4e5fb6..196b5c3 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -401,7 +401,9 @@
 
     private void resetInternal() {
         mSessionCallback = null;
-        mPendingAppPrivateCommands.clear();
+        synchronized (mPendingAppPrivateCommands) {
+            mPendingAppPrivateCommands.clear();
+        }
         if (mSession != null) {
             setSessionSurface(null);
             removeSessionOverlayView();
@@ -691,7 +693,10 @@
         } else {
             Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
                     + "\" pending)");
-            mPendingAppPrivateCommands.add(Pair.create(action, data));
+
+            synchronized (mPendingAppPrivateCommands) {
+                mPendingAppPrivateCommands.add(Pair.create(action, data));
+            }
         }
     }
 
@@ -1320,10 +1325,13 @@
             mSession = session;
             if (session != null) {
                 // Sends the pending app private commands first.
-                for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
-                    mSession.sendAppPrivateCommand(command.first, command.second);
+
+                synchronized (mPendingAppPrivateCommands) {
+                    for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
+                        mSession.sendAppPrivateCommand(command.first, command.second);
+                    }
+                    mPendingAppPrivateCommands.clear();
                 }
-                mPendingAppPrivateCommands.clear();
 
                 synchronized (sMainTvViewLock) {
                     if (hasWindowFocus() && TvView.this == sMainTvView.get()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ea773..49bd9d9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -345,6 +345,8 @@
     <string name="accessibility_wifi_three_bars">Wifi three bars.</string>
     <!-- Content description of the WIFI signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_wifi_signal_full">Wifi signal full.</string>
+    <!-- Content description of the WIFI signal when the WIFI is connected using the signal from a different device owned by the user. For accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
+    <string name="accessibility_wifi_other_device">Connected to your device.</string>
 
     <!-- Content description of the Wi-Fi security type. This message indicates this is an open Wi-Fi (no password needed) [CHAR LIMIT=NONE] -->
     <string name="accessibility_wifi_security_type_none">Open network</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index ee65ef4..ce466df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -50,6 +50,7 @@
     };
 
     public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+    public static final int WIFI_OTHER_DEVICE_CONNECTION = R.string.accessibility_wifi_other_device;
 
     public static final int NO_CALLING = R.string.accessibility_no_calling;
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 104f3d2..ee05f2d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,8 @@
         "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
         "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
         "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
+        "tests/src/com/android/systemui/qs/tiles/base/**/*.kt",
+        "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt",
     ],
     path: "tests/src",
 }
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 5c2f979..0623d4a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -69,7 +69,7 @@
           "exclude-annotation": "org.junit.Ignore"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
           "include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 350b9c2..b3a7a8e9 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -158,26 +158,14 @@
                     BackHandler { onChangeScene(backScene) }
                 }
 
-                Box(
-                    Modifier.drawWithContent {
-                        drawContent()
-
-                        // At this point, all scenes in scenesToCompose are fully laid out so they
-                        // are marked as ready. This is necessary because the animation code needs
-                        // to know the position and size of the elements in each scenes they are in,
-                        // so [readyScenes] will be used to decide whether the transition is ready
-                        // (see isTransitionReady() below).
-                        //
-                        // We can't do that in a DisposableEffect or SideEffect because those are
-                        // run between composition and layout. LaunchedEffect could work and might
-                        // be better, but it looks like launched effects run a frame later than this
-                        // code so doing this here seems better for performance.
-                        scenesToCompose.fastForEach { readyScenes[it.key] = true }
-                    }
-                ) {
+                Box {
                     scenesToCompose.fastForEach { scene ->
                         val key = scene.key
                         key(key) {
+                            // Mark this scene as ready once it has been composed, laid out and
+                            // drawn the first time. We have to do this in a LaunchedEffect here
+                            // because DisposableEffect runs between composition and layout.
+                            LaunchedEffect(key) { readyScenes[key] = true }
                             DisposableEffect(key) { onDispose { readyScenes.remove(key) } }
 
                             scene.Content(
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 569dd4c..a51c55e 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -52,7 +52,7 @@
             style="@style/Widget.Dialog.Button.BorderButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/cancel" />
+            android:text="@string/dismiss_dialog" />
 
         <Space
             android:layout_width="0dp"
@@ -64,6 +64,6 @@
             style="@style/Widget.Dialog.Button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/enable_display" />
+            android:text="@string/mirror_display" />
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 85fb3ac..587caaf 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -44,6 +44,6 @@
 
     <!-- Whether to force split shade.
      For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-     TODO (b/293290851) - change this comment/resource when flag is enabled -->
+     TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index e63229a..fc6d20e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -38,6 +38,6 @@
 
     <!-- Whether to force split shade.
     For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-    TODO (b/293290851) - change this comment/resource when flag is enabled -->
+    TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b6bca65..18f24ec 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -88,7 +88,7 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService)
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService)
     </string>
 
     <!-- The class path of the Safety Quick Settings Tile -->
@@ -604,7 +604,7 @@
 
     <!-- Whether to force split shade.
     For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-    TODO (b/293290851) - change this comment/resource when flag is enabled -->
+    TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">false</bool>
 
     <!-- Whether we use large screen shade header which takes only one row compared to QS header -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5860806..a2637d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,10 @@
 
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
-
     <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
-    <string name="enable_display">Enable display</string>
+    <string name="mirror_display">Mirror display</string>
+    <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+    <string name="dismiss_dialog">Dismiss</string>
 
     <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
     <string name="privacy_dialog_title">Microphone &amp; Camera</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
index fb580ca..5344dcc 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
@@ -18,7 +18,7 @@
 
 import android.hardware.biometrics.BiometricAuthenticator
 
-/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */
+/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI and Settings. */
 enum class BiometricModality {
     None,
     Fingerprint,
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
index 39689ec..fdac37b 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.biometrics.shared.model
 
 /**
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
new file mode 100644
index 0000000..a2b1198
--- /dev/null
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.biometrics.shared.model
+
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+
+/** Fingerprint sensor property. Represents [FingerprintSensorPropertiesInternal]. */
+data class FingerprintSensor(
+    val sensorId: Int,
+    val sensorStrength: SensorStrength,
+    val maxEnrollmentsPerUser: Int,
+    val sensorType: FingerprintSensorType
+)
+
+/** Convert [FingerprintSensorPropertiesInternal] to corresponding [FingerprintSensor] */
+fun FingerprintSensorPropertiesInternal.toFingerprintSensor(): FingerprintSensor {
+    val sensorStrength: SensorStrength = this.sensorStrength.toSensorStrength()
+    val sensorType: FingerprintSensorType = this.sensorType.toSensorType()
+    return FingerprintSensor(this.sensorId, sensorStrength, this.maxEnrollmentsPerUser, sensorType)
+}
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index a9b4fe8..65c5a49 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.biometrics.shared.model
 
 import android.graphics.Rect
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4b14d3cf..bf68869 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -37,9 +37,6 @@
  * Various shared constants between Launcher and SysUI as part of quickstep
  */
 public class QuickStepContract {
-    // Fully qualified name of the Launcher activity.
-    public static final String LAUNCHER_ACTIVITY_CLASS_NAME =
-            "com.google.android.apps.nexuslauncher.NexusLauncherActivity";
 
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
     public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
index cf83f62..31d78b9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
@@ -24,7 +24,8 @@
 interface ISysuiUnlockAnimationController {
     // Provides an implementation of the LauncherUnlockAnimationController to System UI, so that
     // SysUI can use it to control the unlock animation in the launcher window.
-    oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback);
+    oneway void setLauncherUnlockController(
+        String activityClass, ILauncherUnlockAnimationController callback);
 
     // Called by Launcher whenever anything happens to change the state of its smartspace. System UI
     // proactively saves this and uses it to perform the unlock animation without needing to make a
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b2287d87..51dafac 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -68,8 +69,6 @@
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -77,6 +76,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
@@ -84,6 +84,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -420,7 +421,7 @@
                 }
             };
     private final UserInteractor mUserInteractor;
-    private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
+    private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
@@ -457,7 +458,7 @@
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
-            Provider<AuthenticationInteractor> authenticationInteractor
+            Provider<DeviceEntryInteractor> deviceEntryInteractor
     ) {
         super(view);
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -487,7 +488,7 @@
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
         mUserInteractor = userInteractor;
-        mAuthenticationInteractor = authenticationInteractor;
+        mDeviceEntryInteractor = deviceEntryInteractor;
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
@@ -519,9 +520,9 @@
             // When the scene framework says that the lockscreen has been dismissed, dismiss the
             // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mAuthenticationInteractor.get().isLockscreenDismissed(),
-                isLockscreenDismissed -> {
-                    if (isLockscreenDismissed) {
+                mDeviceEntryInteractor.get().isDeviceEntered(),
+                    isDeviceEntered -> {
+                    if (isDeviceEntered) {
                         final int selectedUserId = mUserInteractor.getSelectedUserId();
                         showNextSecurityScreenOrFinish(
                             /* authenticated= */ true,
@@ -1081,15 +1082,11 @@
      * one side).
      */
     private boolean canUseOneHandedBouncer() {
-        switch(mCurrentSecurityMode) {
-            case PIN:
-            case Pattern:
-            case SimPin:
-            case SimPuk:
-                return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
-            default:
-                return false;
-        }
+        return switch (mCurrentSecurityMode) {
+            case PIN, Pattern, SimPin, SimPuk -> getResources().getBoolean(
+                    R.bool.can_use_one_handed_bouncer);
+            default -> false;
+        };
     }
 
     private boolean canDisplayUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index c095aa8..584357b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -80,8 +80,8 @@
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
@@ -205,7 +205,7 @@
     private final Supplier<IWindowSession> mGlobalWindowSessionSupplier;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private final MagnificationGestureDetector mGestureDetector;
-    private final int mBounceEffectDuration;
+    private int mBounceEffectDuration;
     private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
     private Locale mLocale;
     private NumberFormat mPercentFormat;
@@ -272,8 +272,8 @@
 
         setupMagnificationSizeScaleOptions();
 
-        mBounceEffectDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
+        setBounceEffectDuration(mResources.getInteger(
+                com.android.internal.R.integer.config_shortAnimTime));
         updateDimensions();
 
         final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
@@ -1461,6 +1461,11 @@
         mDragView.setColorFilter(filter);
     }
 
+    @VisibleForTesting
+    void setBounceEffectDuration(int duration) {
+        mBounceEffectDuration = duration;
+    }
+
     private void animateBounceEffect() {
         final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
                 PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index b2433d4..80be008 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -29,9 +29,9 @@
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.time.SystemClock
@@ -59,18 +59,6 @@
 
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
-
-    /**
-     * Whether the device is unlocked.
-     *
-     * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method, unless in cases when the current
-     * authentication method is not "secure" (for example, None); in such cases, the value of this
-     * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
-     * by the user to proceed.
-     */
-    val isUnlocked: StateFlow<Boolean>
-
     /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
      *
@@ -129,14 +117,6 @@
     /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
     suspend fun getPinLength(): Int
 
-    /**
-     * Returns whether the lockscreen is enabled.
-     *
-     * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
-     * is considered not secure (for example, "swipe" is considered to be "none").
-     */
-    suspend fun isLockscreenEnabled(): Boolean
-
     /** Reports an authentication attempt. */
     suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
 
@@ -167,6 +147,7 @@
     suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
 }
 
+@SysUISingleton
 class AuthenticationRepositoryImpl
 @Inject
 constructor(
@@ -174,20 +155,10 @@
     private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val userRepository: UserRepository,
-    keyguardRepository: KeyguardRepository,
     private val lockPatternUtils: LockPatternUtils,
     broadcastDispatcher: BroadcastDispatcher,
 ) : AuthenticationRepository {
 
-    override val isUnlocked = keyguardRepository.isKeyguardUnlocked
-
-    override suspend fun isLockscreenEnabled(): Boolean {
-        return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUserId
-            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
-        }
-    }
-
     override val isAutoConfirmEnabled: StateFlow<Boolean> =
         refreshingFlow(
             initialValue = false,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 4cfc6aa..453a7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -26,9 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -42,15 +40,19 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
-/** Hosts application business logic related to authentication. */
+/**
+ * Hosts application business logic related to user authentication.
+ *
+ * Note: there is a distinction between authentication (determining a user's identity) and device
+ * entry (dismissing the lockscreen). For logic that is specific to device entry, please use
+ * `DeviceEntryInteractor` instead.
+ */
 @SysUISingleton
 class AuthenticationInteractor
 @Inject
@@ -59,8 +61,7 @@
     private val repository: AuthenticationRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val userRepository: UserRepository,
-    private val keyguardRepository: KeyguardRepository,
-    sceneInteractor: SceneInteractor,
+    private val deviceEntryRepository: DeviceEntryRepository,
     private val clock: SystemClock,
 ) {
     /**
@@ -77,76 +78,13 @@
      * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
      * the current authentication method is "swipe", the user does not need to complete any
      * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
-     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
-     * lockscreen is showing and still needs to be dismissed by the user to proceed.
+     * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+     * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+     * proceed.
      */
     val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
         repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
 
-    /**
-     * Whether the device is unlocked.
-     *
-     * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method, unless in cases when the current
-     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
-     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
-     * dismissed by the user to proceed.
-     */
-    val isUnlocked: StateFlow<Boolean> =
-        combine(
-                repository.isUnlocked,
-                authenticationMethod,
-            ) { isUnlocked, authenticationMethod ->
-                !authenticationMethod.isSecure || isUnlocked
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = false,
-            )
-
-    /**
-     * Whether the lockscreen has been dismissed (by any method). This can be false even when the
-     * device is unlocked, e.g. when swipe to unlock is enabled.
-     *
-     * Note:
-     * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
-     * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
-     *   transition occurs).
-     */
-    val isLockscreenDismissed: StateFlow<Boolean> =
-        sceneInteractor.desiredScene
-            .map { it.key }
-            .filter { currentScene ->
-                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
-            }
-            .map { it == SceneKey.Gone }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /**
-     * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
-     * authentication. This returns false whenever the lockscreen has been dismissed.
-     *
-     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
-     * UI.
-     */
-    val canSwipeToDismiss =
-        combine(authenticationMethod, isLockscreenDismissed) {
-                authenticationMethod,
-                isLockscreenDismissed ->
-                authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
-                    !isLockscreenDismissed
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
     /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
     val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
 
@@ -211,32 +149,15 @@
      * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
      * the current authentication method is "swipe", the user does not need to complete any
      * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
-     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
-     * lockscreen is showing and still needs to be dismissed by the user to proceed.
+     * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+     * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+     * proceed.
      */
     suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
         return repository.getAuthenticationMethod().toDomainLayer()
     }
 
     /**
-     * Returns `true` if the device currently requires authentication before content can be viewed;
-     * `false` if content can be displayed without unlocking first.
-     */
-    suspend fun isAuthenticationRequired(): Boolean {
-        return !isUnlocked.value && getAuthenticationMethod().isSecure
-    }
-
-    /**
-     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
-     * dismisses once the authentication challenge is completed. For example, completing a biometric
-     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
-     * lock screen.
-     */
-    fun isBypassEnabled(): Boolean {
-        return keyguardRepository.isBypassEnabled()
-    }
-
-    /**
      * Attempts to authenticate the user and unlock the device.
      *
      * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -312,7 +233,7 @@
 
     /** Starts refreshing the throttling state every second. */
     private suspend fun startThrottlingCountdown() {
-        cancelCountdown()
+        cancelThrottlingCountdown()
         throttlingCountdownJob =
             applicationScope.launch {
                 while (refreshThrottling() > 0) {
@@ -322,14 +243,14 @@
     }
 
     /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
-    private fun cancelCountdown() {
+    private fun cancelThrottlingCountdown() {
         throttlingCountdownJob?.cancel()
         throttlingCountdownJob = null
     }
 
     /** Notifies that the currently-selected user has changed. */
     private suspend fun onSelectedUserChanged() {
-        cancelCountdown()
+        cancelThrottlingCountdown()
         if (refreshThrottling() > 0) {
             startThrottlingCountdown()
         }
@@ -378,7 +299,7 @@
         DomainLayerAuthenticationMethodModel {
         return when (this) {
             is DataLayerAuthenticationMethodModel.None ->
-                if (repository.isLockscreenEnabled()) {
+                if (deviceEntryRepository.isInsecureLockscreenEnabled()) {
                     DomainLayerAuthenticationMethodModel.Swipe
                 } else {
                     DomainLayerAuthenticationMethodModel.None
@@ -394,13 +315,10 @@
 
 /** Result of a user authentication attempt. */
 enum class AuthenticationResult {
-    /** Authentication succeeded and the device is now unlocked. */
+    /** Authentication succeeded. */
     SUCCEEDED,
-    /** Authentication failed and the device remains unlocked. */
+    /** Authentication failed. */
     FAILED,
-    /**
-     * Authentication was not performed, e.g. due to insufficient input, and the device remains
-     * unlocked.
-     */
+    /** Authentication was not performed, e.g. due to insufficient input. */
     SKIPPED,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index f3a463b..0c02369 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -50,6 +51,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
     private val repository: BouncerRepository,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
     flags: SceneContainerFlags,
@@ -144,7 +146,7 @@
         message: String? = null,
     ) {
         applicationScope.launch {
-            if (authenticationInteractor.isAuthenticationRequired()) {
+            if (deviceEntryInteractor.isAuthenticationRequired()) {
                 repository.setMessage(
                     message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 782ead3..c98cf31 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -62,6 +62,10 @@
                 initialValue = !bouncerInteractor.isThrottled.value,
             )
 
+    // Handle to the scope of the child ViewModel (stored in [authMethod]).
+    private var childViewModelScope: CoroutineScope? = null
+    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+
     /** View-model for the current UI, based on the current authentication method. */
     val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
         authenticationInteractor.authenticationMethod
@@ -72,8 +76,31 @@
                 initialValue = null,
             )
 
-    // Handle to the scope of the child ViewModel (stored in [authMethod]).
-    private var childViewModelScope: CoroutineScope? = null
+    /**
+     * A message for a throttling dialog to show when the user has attempted the wrong credential
+     * too many times and now must wait a while before attempting again.
+     *
+     * If `null`, no dialog should be shown.
+     *
+     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
+     * dismisses this dialog.
+     */
+    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+
+    /** The user-facing message to show in the bouncer. */
+    val message: StateFlow<MessageViewModel> =
+        combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
+                toMessageViewModel(message, isThrottled)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue =
+                    toMessageViewModel(
+                        message = bouncerInteractor.message.value,
+                        isThrottled = bouncerInteractor.isThrottled.value,
+                    ),
+            )
 
     init {
         if (flags.isEnabled()) {
@@ -98,33 +125,6 @@
         }
     }
 
-    /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<MessageViewModel> =
-        combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled ->
-                toMessageViewModel(message, isThrottled)
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    toMessageViewModel(
-                        message = bouncerInteractor.message.value,
-                        isThrottled = bouncerInteractor.isThrottled.value,
-                    ),
-            )
-
-    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
-    /**
-     * A message for a throttling dialog to show when the user has attempted the wrong credential
-     * too many times and now must wait a while before attempting again.
-     *
-     * If `null`, no dialog should be shown.
-     *
-     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
-     * dismisses this dialog.
-     */
-    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
-
     /** Notifies that the emergency services button was clicked. */
     fun onEmergencyServicesButtonClicked() {
         // TODO(b/280877228): implement this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 9b93522..e8a8444 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,26 +18,25 @@
 
 import android.app.Activity
 import android.app.ActivityOptions
-import android.app.ActivityTaskManager
-import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.Dialog
 import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.graphics.Rect
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowInsets
 import android.view.WindowInsets.Type
 import android.view.WindowManager
 import android.widget.ImageView
+import androidx.annotation.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.systemui.res.R
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.boundsOnScreen
 import com.android.wm.shell.taskview.TaskView
 
 /**
@@ -65,8 +64,8 @@
         private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL"
     }
 
-    var detailTaskId = INVALID_TASK_ID
     private lateinit var taskViewContainer: View
+    private lateinit var controlDetailRoot: View
     private val taskWidthPercentWidth = activityContext.resources.getFloat(
         R.dimen.controls_task_view_width_percentage
     )
@@ -79,12 +78,7 @@
         addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
     }
 
-    fun removeDetailTask() {
-        if (detailTaskId == INVALID_TASK_ID) return
-        ActivityTaskManager.getInstance().removeTask(detailTaskId)
-        detailTaskId = INVALID_TASK_ID
-    }
-
+    @VisibleForTesting
     val stateCallback = object : TaskView.Listener {
         override fun onInitialized() {
             taskViewContainer.apply {
@@ -98,33 +92,29 @@
                 activityContext,
                 0 /* enterResId */,
                 0 /* exitResId */
-            ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
-            options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+            ).apply {
+                pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+                taskAlwaysOnTop = true
+            }
 
             taskView.startActivity(
                 pendingIntent,
                 fillInIntent,
                 options,
-                getTaskViewBounds()
+                taskView.boundsOnScreen,
             )
         }
 
         override fun onTaskRemovalStarted(taskId: Int) {
-            detailTaskId = INVALID_TASK_ID
-            dismiss()
+            taskView.release()
         }
 
         override fun onTaskCreated(taskId: Int, name: ComponentName?) {
-            detailTaskId = taskId
             requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
                 setAlpha(1f)
             }
         }
-
-        override fun onReleased() {
-            removeDetailTask()
-        }
-
         override fun onBackPressedOnTaskRoot(taskId: Int) {
             dismiss()
         }
@@ -138,6 +128,9 @@
         setContentView(R.layout.controls_detail_dialog)
 
         taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container)
+        controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply {
+            setOnClickListener { _: View -> dismiss() }
+        }
 
         requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
             addView(taskView)
@@ -147,13 +140,9 @@
         requireViewById<ImageView>(R.id.control_detail_close).apply {
             setOnClickListener { _: View -> dismiss() }
         }
-        requireViewById<View>(R.id.control_detail_root).apply {
-            setOnClickListener { _: View -> dismiss() }
-        }
 
         requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
             setOnClickListener { v: View ->
-                removeDetailTask()
                 dismiss()
 
                 val action = ActivityStarter.OnDismissAction {
@@ -201,26 +190,9 @@
         taskView.setListener(cvh.uiExecutor, stateCallback)
     }
 
-    fun getTaskViewBounds(): Rect {
-        val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
-        val windowMetrics = wm.getCurrentWindowMetrics()
-        val rect = windowMetrics.bounds
-        val metricInsets = windowMetrics.windowInsets
-        val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars()
-                or Type.displayCutout())
-        val headerHeight = context.resources.getDimensionPixelSize(
-                R.dimen.controls_detail_dialog_header_height)
-
-        val finalRect = Rect(rect.left - insets.left /* left */,
-                rect.top + insets.top + headerHeight /* top */,
-                rect.right - insets.right /* right */,
-                rect.bottom - insets.bottom /* bottom */)
-        return finalRect
-    }
-
     override fun dismiss() {
         if (!isShowing()) return
-        taskView.release()
+        taskView.removeTask()
 
         val isActivityFinishing =
             (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 1b0d032..848c786 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.controls.ui
 
 import android.app.ActivityOptions
-import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
@@ -45,8 +44,6 @@
         taskView.alpha = 0f
     }
 
-    private var detailTaskId = INVALID_TASK_ID
-
     private val fillInIntent =
         Intent().apply {
             // Apply flags to make behaviour match documentLaunchMode=always.
@@ -57,7 +54,6 @@
     private val stateCallback =
         object : TaskView.Listener {
             override fun onInitialized() {
-
                 val options =
                     ActivityOptions.makeCustomAnimation(
                         activityContext,
@@ -88,12 +84,10 @@
             }
 
             override fun onTaskRemovalStarted(taskId: Int) {
-                detailTaskId = INVALID_TASK_ID
                 release()
             }
 
             override fun onTaskCreated(taskId: Int, name: ComponentName?) {
-                detailTaskId = taskId
                 taskView.alpha = 1f
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index da5e933..04a9cae 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -25,6 +25,7 @@
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.INotificationManager;
+import android.app.IUriGrantsManager;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
@@ -688,4 +689,12 @@
     static StatusBarManager provideStatusBarManager(Context context) {
         return context.getSystemService(StatusBarManager.class);
     }
+
+    @Provides
+    @Singleton
+    static IUriGrantsManager provideIUriGrantsManager() {
+        return IUriGrantsManager.Stub.asInterface(
+                ServiceManager.getService(Context.URI_GRANTS_SERVICE)
+        );
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 283a07b..4b6ad6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.deviceentry.DeviceEntryModule;
 import com.android.systemui.display.DisplayModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
@@ -173,6 +174,7 @@
         ControlsModule.class,
         CoroutinesModule.class,
         DemoModeModule.class,
+        DeviceEntryModule.class,
         DisableFlagsModule.class,
         DisplayModule.class,
         DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
new file mode 100644
index 0000000..e7f835f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.deviceentry
+
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
+import dagger.Module
+
+@Module(
+    includes =
+        [
+            DeviceEntryRepositoryModule::class,
+        ],
+)
+object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
new file mode 100644
index 0000000..5b85ad0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.UserRepository
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Interface for classes that can access device-entry-related application state. */
+interface DeviceEntryRepository {
+    /**
+     * Whether the device is unlocked.
+     *
+     * A device that is not yet unlocked requires unlocking by completing an authentication
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None); in such cases, the value of this
+     * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
+     * by the user to proceed.
+     */
+    val isUnlocked: StateFlow<Boolean>
+
+    /**
+     * Whether the lockscreen should be shown when the authentication method is not secure (e.g.
+     * `None` or `Swipe`).
+     */
+    suspend fun isInsecureLockscreenEnabled(): Boolean
+
+    /**
+     * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
+     * dismissed once the authentication challenge is completed.
+     *
+     * This is a setting that is specific to the face unlock authentication method, because the user
+     * intent to unlock is not known. On devices that don't support face unlock, this always returns
+     * `true`.
+     *
+     * When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss
+     * the lockscreen.
+     */
+    fun isBypassEnabled(): Boolean
+}
+
+/** Encapsulates application state for device entry. */
+@SysUISingleton
+class DeviceEntryRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardBypassController: KeyguardBypassController,
+    keyguardStateController: KeyguardStateController,
+) : DeviceEntryRepository {
+
+    override val isUnlocked =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onUnlockedChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isUnlocked due to onUnlockedChanged"
+                            )
+                        }
+
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isUnlocked due to onKeyguardShowingChanged"
+                            )
+                        }
+                    }
+
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isUnlocked,
+                    TAG,
+                    "initial isKeyguardUnlocked"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    override suspend fun isInsecureLockscreenEnabled(): Boolean {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.getSelectedUserInfo().id
+            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
+        }
+    }
+
+    override fun isBypassEnabled() = keyguardBypassController.bypassEnabled
+
+    companion object {
+        private const val TAG = "DeviceEntryRepositoryImpl"
+    }
+}
+
+@Module
+interface DeviceEntryRepositoryModule {
+    @Binds fun repository(impl: DeviceEntryRepositoryImpl): DeviceEntryRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
new file mode 100644
index 0000000..5612c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -0,0 +1,110 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Hosts application business logic related to device entry.
+ *
+ * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless
+ * of the authentication method used.
+ */
+@SysUISingleton
+class DeviceEntryInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val repository: DeviceEntryRepository,
+    private val authenticationInteractor: AuthenticationInteractor,
+    sceneInteractor: SceneInteractor,
+) {
+    /**
+     * Whether the device is unlocked.
+     *
+     * A device that is not yet unlocked requires unlocking by completing an authentication
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+     * dismissed by the user to proceed.
+     */
+    val isUnlocked: StateFlow<Boolean> =
+        combine(
+                repository.isUnlocked,
+                authenticationInteractor.authenticationMethod,
+            ) { isUnlocked, authenticationMethod ->
+                !authenticationMethod.isSecure || isUnlocked
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    /**
+     * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
+     * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away
+     * the non-secure lockscreen, even though they've already authenticated.
+     *
+     * Note: This does not imply that the lockscreen is visible or not.
+     */
+    val isDeviceEntered: StateFlow<Boolean> =
+        sceneInteractor.desiredScene
+            .map { it.key }
+            .filter { currentScene ->
+                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+            }
+            .map { it == SceneKey.Gone }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /**
+     * Whether it's currently possible to swipe up to enter the device without requiring
+     * authentication. This returns `false` whenever the lockscreen has been dismissed.
+     *
+     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+     * UI.
+     */
+    val canSwipeToEnter =
+        combine(authenticationInteractor.authenticationMethod, isDeviceEntered) {
+                authenticationMethod,
+                isDeviceEntered ->
+                authenticationMethod is AuthenticationMethodModel.Swipe && !isDeviceEntered
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /**
+     * Returns `true` if the device currently requires authentication before entry is granted;
+     * `false` if the device can be entered without authenticating first.
+     */
+    suspend fun isAuthenticationRequired(): Boolean {
+        return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure
+    }
+
+    /**
+     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+     * dismissed once the authentication challenge is completed. For example, completing a biometric
+     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+     * lock screen.
+     */
+    fun isBypassEnabled() = repository.isBypassEnabled()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 871257e..a00c3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -80,16 +80,9 @@
     @JvmField
     val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
 
-    /** Makes sure notification panel is updated before the user switch is complete. */
-    // TODO(b/278873737): Tracking Bug
-    @JvmField
-    val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
-        releasedFlag("load_notifications_before_the_user_switch_is_complete")
-
     // TODO(b/277338665): Tracking Bug
     @JvmField
-    val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag("notification_shelf_refactor", teamfood = true)
+    val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor")
 
     // TODO(b/290787599): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d9b2c39..d89cf63 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -176,7 +176,6 @@
             SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
             SliderState.JUMP_TRACK_LOCATION_SELECTED ->
                 sliderListener.onProgressJump(latestProgress)
-            SliderState.JUMP_BOOKEND_SELECTED -> executeOnBookend()
             else -> {}
         }
     }
@@ -197,7 +196,7 @@
         epsilon: Float = 0.00001f,
     ): Boolean {
         val delta = abs(currentProgress - latestProgress)
-        return abs(delta - config.jumpThreshold) < epsilon
+        return delta > config.jumpThreshold - epsilon
     }
 
     private fun bookendReached(currentProgress: Float): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
index 9c99c90..9f12f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt
@@ -37,7 +37,9 @@
      * Notification that the slider reached a certain progress on the slider track.
      *
      * This method is called in all intermediate steps of a continuous progress change as the slider
-     * moves through the slider track.
+     * moves through the slider track. A single discrete movement of the handle by an external
+     * button or by a jump on the slider track will not trigger this callback. See
+     * [onSelectAndArrow] and [onProgressJump] for these cases.
      *
      * @param[progress] The progress of the slider in the range from 0F to 1F (inclusive).
      */
@@ -56,7 +58,7 @@
     fun onProgressJump(@FloatRange(from = 0.0, to = 1.0) progress: Float)
 
     /**
-     * Notification that the slider handle was moved by a button press.
+     * Notification that the slider handle was moved discretely by one step via a button press.
      *
      * @param[progress] The progress of the slider in the range from 0F to 1F (inclusive).
      */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index ff74050..2a69ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.shared.recents.utilities.Utilities
 import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.shared.system.QuickStepContract
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
 import com.android.systemui.shared.system.smartspace.SmartspaceState
@@ -219,6 +218,11 @@
      */
     private var launcherUnlockController: ILauncherUnlockAnimationController? = null
 
+    /**
+     * Fully qualified class name of the launcher activity
+     */
+    private var launcherActivityClass: String? = null
+
     private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
 
     /**
@@ -226,7 +230,11 @@
      * doesn't happen, we won't use in-window animations or the smartspace shared element
      * transition, but that's okay!
      */
-    override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) {
+    override fun setLauncherUnlockController(
+            activityClass: String,
+            callback: ILauncherUnlockAnimationController?
+    ) {
+        launcherActivityClass = activityClass
         launcherUnlockController = callback
     }
 
@@ -545,7 +553,6 @@
         // gesture and the surface behind the keyguard should be made visible so that we can animate
         // it in.
         if (requestedShowSurfaceBehindKeyguard) {
-
             // If we're flinging to dismiss here, it means the touch gesture ended in a fling during
             // the time it takes the keyguard exit animation to start. This is an edge case race
             // condition, which we handle by just playing a canned animation on the now-visible
@@ -785,7 +792,6 @@
 
         if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
             !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
-
             keyguardViewMediator.get().showSurfaceBehindKeyguard()
         } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                 keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
@@ -1113,17 +1119,18 @@
         return playingCannedUnlockAnimation
     }
 
+    /**
+     * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
+     * launcher or an app. If so, we can communicate with it to perform in-window/shared element
+     * transitions!
+     */
+    fun isNexusLauncherUnderneath(): Boolean {
+        return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
+                .runningTask?.topActivity?.className?.equals(it) }
+                ?: false
+    }
+
     companion object {
-        /**
-         * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
-         * launcher or an app. If so, we can communicate with it to perform in-window/shared element
-         * transitions!
-         */
-        fun isNexusLauncherUnderneath(): Boolean {
-            return ActivityManagerWrapper.getInstance()
-                    .runningTask?.topActivity?.className?.equals(
-                            QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
-        }
 
         fun isFoldable(context: Context): Boolean {
             return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b506a36..7e826b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -128,7 +128,6 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -146,6 +145,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -181,7 +181,6 @@
 import java.util.function.Consumer;
 
 import dagger.Lazy;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -2833,7 +2832,7 @@
             // playing in-window animations for this particular unlock since a previous unlock might
             // have changed the Launcher state.
             if (mWakeAndUnlocking
-                    && KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) {
+                    && mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
@@ -3287,7 +3286,7 @@
             // of the in-window animations are reflected. This is needed even if we're not actually
             // playing in-window animations for this particular unlock since a previous unlock might
             // have changed the Launcher state.
-            if (KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) {
+            if (mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2557e81..36b93cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -47,7 +47,6 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -97,9 +96,6 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
-    /** Is the keyguard in a unlocked state? */
-    val isKeyguardUnlocked: StateFlow<Boolean>
-
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
 
@@ -206,14 +202,6 @@
      */
     fun isKeyguardShowing(): Boolean
 
-    /**
-     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
-     * dismissed once the authentication challenge is completed. For example, completing a biometric
-     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
-     * lock screen.
-     */
-    fun isBypassEnabled(): Boolean
-
     /** Sets whether the bottom area UI should animate the transition out of doze state. */
     fun setAnimateDozingTransitions(animate: Boolean)
 
@@ -265,7 +253,6 @@
     screenLifecycle: ScreenLifecycle,
     biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
-    private val keyguardBypassController: KeyguardBypassController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val dozeParameters: DozeParameters,
@@ -370,44 +357,6 @@
             }
             .distinctUntilChanged()
 
-    override val isKeyguardUnlocked: StateFlow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardStateController.Callback {
-                        override fun onUnlockedChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isUnlocked,
-                                TAG,
-                                "updated isKeyguardUnlocked due to onUnlockedChanged"
-                            )
-                        }
-
-                        override fun onKeyguardShowingChanged() {
-                            trySendWithFailureLogging(
-                                keyguardStateController.isUnlocked,
-                                TAG,
-                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
-                            )
-                        }
-                    }
-
-                keyguardStateController.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    keyguardStateController.isUnlocked,
-                    TAG,
-                    "initial isKeyguardUnlocked"
-                )
-
-                awaitClose { keyguardStateController.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
-            .stateIn(
-                scope,
-                SharingStarted.Eagerly,
-                initialValue = false,
-            )
-
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
             object : KeyguardStateController.Callback {
@@ -543,10 +492,6 @@
         return keyguardStateController.isShowing
     }
 
-    override fun isBypassEnabled(): Boolean {
-        return keyguardBypassController.bypassEnabled
-    }
-
     // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
     // [SceneInteractor] when scenes are ready.
     override val statusBarState: StateFlow<StatusBarState> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 338f994..8063468 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,7 +23,6 @@
 import android.graphics.Point
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
-import com.android.systemui.res.R
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -31,6 +30,7 @@
 import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -40,10 +40,10 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -79,6 +79,7 @@
     private val commandQueue: CommandQueue,
     featureFlags: FeatureFlags,
     sceneContainerFlags: SceneContainerFlags,
+    deviceEntryRepository: DeviceEntryRepository,
     bouncerRepository: KeyguardBouncerRepository,
     configurationRepository: ConfigurationRepository,
     shadeRepository: ShadeRepository,
@@ -168,7 +169,7 @@
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
     /** Whether the keyguard is unlocked or not. */
-    val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+    val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked
 
     /** Whether the keyguard is occluded (covered by an activity). */
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -323,26 +324,7 @@
         repository.setAnimateDozingTransitions(animate)
     }
 
-    fun isKeyguardDismissable(): Boolean {
-        return repository.isKeyguardUnlocked.value
-    }
-
     companion object {
         private const val TAG = "KeyguardInteractor"
-
-        fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
-            return when (state) {
-                KeyguardState.OFF -> true
-                KeyguardState.DOZING -> true
-                KeyguardState.DREAMING -> true
-                KeyguardState.AOD -> true
-                KeyguardState.ALTERNATE_BOUNCER -> true
-                KeyguardState.PRIMARY_BOUNCER -> true
-                KeyguardState.LOCKSCREEN -> true
-                KeyguardState.GONE -> false
-                KeyguardState.OCCLUDED -> true
-                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 91b3357..c03e4d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,26 +34,22 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    authenticationInteractor: AuthenticationInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
     communalInteractor: CommunalInteractor,
     val longPress: KeyguardLongPressViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
-        authenticationInteractor.isUnlocked
+        deviceEntryInteractor.isUnlocked
             .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = upDestinationSceneKey(authenticationInteractor.isUnlocked.value),
+                initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
             )
 
     private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
-        return if (isUnlocked) {
-            SceneKey.Gone
-        } else {
-            SceneKey.Bouncer
-        }
+        return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer
     }
 
     /** The key of the scene we should switch to when swiping left. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index ae3c912..ed6d41e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -101,7 +101,8 @@
     panelEventsEvents: ShadeStateEvents,
     private val secureSettings: SecureSettings,
     @Main private val handler: Handler,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    private val logger: MediaViewLogger,
 ) {
 
     /** Track the media player setting status on lock screen. */
@@ -1057,6 +1058,7 @@
                     // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
                 }
+                logger.logMediaHostAttachment(currentAttachmentLocation)
                 if (isCrossFadeAnimatorRunning) {
                     // When cross-fading with an animation, we only notify the media carousel of the
                     // location change, once the view is reattached to the new place and not
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index 8f1595d..3ff23159 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -52,4 +52,8 @@
             { "location ($str1): $int1 -> $int2" }
         )
     }
+
+    fun logMediaHostAttachment(host: Int) {
+        buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 34d6233..128c237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -17,6 +17,7 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 
+import android.app.IUriGrantsManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +32,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.service.quicksettings.IQSTileService;
@@ -43,11 +45,9 @@
 import android.view.WindowManagerGlobal;
 import android.widget.Switch;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -67,9 +67,10 @@
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
@@ -109,24 +110,30 @@
     private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false);
     private final TileServices mTileServices;
 
-    private CustomTile(
-            QSHost host,
+    private int mServiceUid = Process.INVALID_UID;
+
+    private final IUriGrantsManager mIUriGrantsManager;
+
+    @AssistedInject
+    CustomTile(
+            Lazy<QSHost> host,
             QsEventLogger uiEventLogger,
-            Looper backgroundLooper,
-            Handler mainHandler,
+            @Background Looper backgroundLooper,
+            @Main Handler mainHandler,
             FalsingManager falsingManager,
             MetricsLogger metricsLogger,
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            String action,
-            Context userContext,
+            @Assisted String action,
+            @Assisted Context userContext,
             CustomTileStatePersister customTileStatePersister,
             TileServices tileServices,
-            DisplayTracker displayTracker
+            DisplayTracker displayTracker,
+            IUriGrantsManager uriGrantsManager
     ) {
-        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
-                statusBarStateController, activityStarter, qsLogger);
+        super(host.get(), uiEventLogger, backgroundLooper, mainHandler, falsingManager,
+                metricsLogger, statusBarStateController, activityStarter, qsLogger);
         mTileServices = tileServices;
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
         mComponent = ComponentName.unflattenFromString(action);
@@ -139,6 +146,7 @@
         mService = mServiceManager.getTileService();
         mCustomTileStatePersister = customTileStatePersister;
         mDisplayTracker = displayTracker;
+        mIUriGrantsManager = uriGrantsManager;
     }
 
     @Override
@@ -268,7 +276,8 @@
      *
      * @param tile tile populated with state to apply
      */
-    public void updateTileState(Tile tile) {
+    public void updateTileState(Tile tile, int appUid) {
+        mServiceUid = appUid;
         // This comes from a binder call IQSService.updateQsTile
         mHandler.post(() -> handleUpdateTileState(tile));
     }
@@ -433,14 +442,25 @@
         state.state = tileState;
         Drawable drawable = null;
         try {
-            drawable = mTile.getIcon().loadDrawable(mUserContext);
+            drawable = mTile.getIcon().loadDrawableCheckingUriGrant(
+                    mUserContext,
+                    mIUriGrantsManager,
+                    mServiceUid,
+                    mComponent.getPackageName()
+            );
         } catch (Exception e) {
             Log.w(TAG, "Invalid icon, forcing into unavailable state");
             state.state = Tile.STATE_UNAVAILABLE;
-            drawable = mDefaultIcon.loadDrawable(mUserContext);
         }
 
-        final Drawable drawableF = drawable;
+        final Drawable drawableF;
+        if (drawable != null) {
+            drawableF = drawable;
+        } else if (mDefaultIcon != null) {
+            drawableF = mDefaultIcon.loadDrawable(mUserContext);
+        } else {
+            drawableF = null;
+        }
         state.iconSupplier = () -> {
             if (drawableF == null) return null;
             Drawable.ConstantState cs = drawableF.getConstantState();
@@ -543,96 +563,17 @@
     /**
      * Create a {@link CustomTile} for a given spec and user.
      *
-     * @param builder     including injected common dependencies.
+     * @param factory     including injected common dependencies.
      * @param spec        as provided by {@link CustomTile#toSpec}
      * @param userContext context for the user that is creating this tile.
      * @return a new {@link CustomTile}
      */
-    public static CustomTile create(Builder builder, String spec, Context userContext) {
-        return builder
-                .setSpec(spec)
-                .setUserContext(userContext)
-                .build();
+    public static CustomTile create(Factory factory, String spec, Context userContext) {
+        return factory.create(getAction(spec), userContext);
     }
 
-    public static class Builder {
-        final Lazy<QSHost> mQSHostLazy;
-        final QsEventLogger mUiEventLogger;
-        final Looper mBackgroundLooper;
-        final Handler mMainHandler;
-        private final FalsingManager mFalsingManager;
-        final MetricsLogger mMetricsLogger;
-        final StatusBarStateController mStatusBarStateController;
-        final ActivityStarter mActivityStarter;
-        final QSLogger mQSLogger;
-        final CustomTileStatePersister mCustomTileStatePersister;
-        private TileServices mTileServices;
-        final DisplayTracker mDisplayTracker;
-
-        Context mUserContext;
-        String mSpec = "";
-
-        @Inject
-        public Builder(
-                Lazy<QSHost> hostLazy,
-                QsEventLogger uiEventLogger,
-                @Background Looper backgroundLooper,
-                @Main Handler mainHandler,
-                FalsingManager falsingManager,
-                MetricsLogger metricsLogger,
-                StatusBarStateController statusBarStateController,
-                ActivityStarter activityStarter,
-                QSLogger qsLogger,
-                CustomTileStatePersister customTileStatePersister,
-                TileServices tileServices,
-                DisplayTracker displayTracker
-        ) {
-            mQSHostLazy = hostLazy;
-            mUiEventLogger = uiEventLogger;
-            mBackgroundLooper = backgroundLooper;
-            mMainHandler = mainHandler;
-            mFalsingManager = falsingManager;
-            mMetricsLogger = metricsLogger;
-            mStatusBarStateController = statusBarStateController;
-            mActivityStarter = activityStarter;
-            mQSLogger = qsLogger;
-            mCustomTileStatePersister = customTileStatePersister;
-            mTileServices = tileServices;
-            mDisplayTracker = displayTracker;
-        }
-
-        Builder setSpec(@NonNull String spec) {
-            mSpec = spec;
-            return this;
-        }
-
-        Builder setUserContext(@NonNull Context userContext) {
-            mUserContext = userContext;
-            return this;
-        }
-
-        @VisibleForTesting
-        public CustomTile build() {
-            if (mUserContext == null) {
-                throw new NullPointerException("UserContext cannot be null");
-            }
-            String action = getAction(mSpec);
-            return new CustomTile(
-                    mQSHostLazy.get(),
-                    mUiEventLogger,
-                    mBackgroundLooper,
-                    mMainHandler,
-                    mFalsingManager,
-                    mMetricsLogger,
-                    mStatusBarStateController,
-                    mActivityStarter,
-                    mQSLogger,
-                    action,
-                    mUserContext,
-                    mCustomTileStatePersister,
-                    mTileServices,
-                    mDisplayTracker
-            );
-        }
+    @AssistedFactory
+    public interface Factory {
+        CustomTile create(String action, Context userContext);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 1659c3e..c3c587d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.external
 
+import android.app.IUriGrantsManager
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.view.ContextThemeWrapper
@@ -34,7 +35,7 @@
  * Dialog to present to the user to ask for authorization to add a [TileService].
  */
 class TileRequestDialog(
-    context: Context
+    context: Context,
 ) : SystemUIDialog(context) {
 
     companion object {
@@ -44,7 +45,7 @@
     /**
      * Set the data of the tile to add, to show the user.
      */
-    fun setTileData(tileData: TileData) {
+    fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
         val ll = (LayoutInflater
                         .from(context)
                         .inflate(R.layout.tile_service_request_dialog, null)
@@ -54,7 +55,7 @@
                                 .getString(R.string.qs_tile_request_dialog_text, tileData.appName)
                     }
                     addView(
-                            createTileView(tileData),
+                            createTileView(tileData, iUriGrantsManager),
                             context.resources.getDimensionPixelSize(
                                     R.dimen.qs_tile_service_request_tile_width),
                             context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
@@ -65,13 +66,21 @@
         setView(ll, spacing, spacing, spacing, spacing / 2)
     }
 
-    private fun createTileView(tileData: TileData): QSTileView {
+    private fun createTileView(
+            tileData: TileData,
+            iUriGrantsManager: IUriGrantsManager,
+    ): QSTileView {
         val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
         val tile = QSTileViewImpl(themedContext, true)
         val state = QSTile.BooleanState().apply {
             label = tileData.label
             handlesLongClick = false
-            icon = tileData.icon?.loadDrawable(context)?.let {
+            icon = tileData.icon?.loadDrawableCheckingUriGrant(
+                    context,
+                    iUriGrantsManager,
+                    tileData.callingUid,
+                    tileData.packageName,
+            )?.let {
                 QSTileImpl.DrawableIcon(it)
             } ?: ResourceIcon.get(R.drawable.android)
             contentDescription = label
@@ -93,8 +102,10 @@
      * @property icon Icon for the tile.
      */
     data class TileData(
+        val callingUid: Int,
         val appName: CharSequence,
         val label: CharSequence,
-        val icon: Icon?
+        val icon: Icon?,
+        val packageName: String,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 899d0e2..08567af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.external
 
 import android.app.Dialog
+import android.app.IUriGrantsManager
 import android.app.StatusBarManager
 import android.content.ComponentName
 import android.content.DialogInterface
@@ -42,12 +43,13 @@
 /**
  * Controller to interface between [TileRequestDialog] and [QSHost].
  */
-class TileServiceRequestController constructor(
-    private val qsHost: QSHost,
-    private val commandQueue: CommandQueue,
-    private val commandRegistry: CommandRegistry,
-    private val eventLogger: TileRequestDialogEventLogger,
-    private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+class TileServiceRequestController(
+        private val qsHost: QSHost,
+        private val commandQueue: CommandQueue,
+        private val commandRegistry: CommandRegistry,
+        private val eventLogger: TileRequestDialogEventLogger,
+        private val iUriGrantsManager: IUriGrantsManager,
+        private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
 ) {
 
     companion object {
@@ -62,13 +64,14 @@
 
     private val commandQueueCallback = object : CommandQueue.Callbacks {
         override fun requestAddTile(
+            callingUid: Int,
             componentName: ComponentName,
             appName: CharSequence,
             label: CharSequence,
             icon: Icon,
             callback: IAddTileResultCallback
         ) {
-            requestTileAdd(componentName, appName, label, icon) {
+            requestTileAdd(callingUid, componentName, appName, label, icon) {
                 try {
                     callback.onTileRequest(it)
                 } catch (e: RemoteException) {
@@ -98,6 +101,7 @@
 
     @VisibleForTesting
     internal fun requestTileAdd(
+        callingUid: Int,
         componentName: ComponentName,
         appName: CharSequence,
         label: CharSequence,
@@ -119,7 +123,13 @@
             eventLogger.logUserResponse(response, packageName, instanceId)
             callback.accept(response)
         }
-        val tileData = TileRequestDialog.TileData(appName, label, icon)
+        val tileData = TileRequestDialog.TileData(
+                callingUid,
+                appName,
+                label,
+                icon,
+                componentName.packageName,
+        )
         createDialog(tileData, dialogResponse).also { dialog ->
             dialogCanceller = {
                 if (packageName == it) {
@@ -143,7 +153,7 @@
             }
         }
         return dialogCreator().apply {
-            setTileData(tileData)
+            setTileData(tileData, iUriGrantsManager)
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
             setOnCancelListener { responseHandler.accept(DISMISSED) }
@@ -168,7 +178,7 @@
                         Log.w(TAG, "Malformed componentName ${args[0]}")
                         return
                     }
-            requestTileAdd(componentName, args[1], args[2], null) {
+            requestTileAdd(0, componentName, args[1], args[2], null) {
                 Log.d(TAG, "Response: $it")
             }
         }
@@ -192,14 +202,16 @@
     @SysUISingleton
     class Builder @Inject constructor(
         private val commandQueue: CommandQueue,
-        private val commandRegistry: CommandRegistry
+        private val commandRegistry: CommandRegistry,
+        private val iUriGrantsManager: IUriGrantsManager,
     ) {
         fun create(qsHost: QSHost): TileServiceRequestController {
             return TileServiceRequestController(
                     qsHost,
                     commandQueue,
                     commandRegistry,
-                    TileRequestDialogEventLogger()
+                    TileRequestDialogEventLogger(),
+                    iUriGrantsManager,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index c8691ac..fc24022 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -184,7 +184,7 @@
         }
     }
 
-    private void verifyCaller(CustomTile tile) {
+    private int verifyCaller(CustomTile tile) {
         try {
             String packageName = tile.getComponent().getPackageName();
             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
@@ -192,6 +192,7 @@
             if (Binder.getCallingUid() != uid) {
                 throw new SecurityException("Component outside caller's uid");
             }
+            return uid;
         } catch (PackageManager.NameNotFoundException e) {
             throw new SecurityException(e);
         }
@@ -228,7 +229,7 @@
     public void updateQsTile(Tile tile, IBinder token) {
         CustomTile customTile = getTileForToken(token);
         if (customTile != null) {
-            verifyCaller(customTile);
+            int uid = verifyCaller(customTile);
             synchronized (mServices) {
                 final TileServiceManager tileServiceManager = mServices.get(customTile);
                 if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) {
@@ -239,7 +240,7 @@
                 tileServiceManager.clearPendingBind();
                 tileServiceManager.setLastUpdate(System.currentTimeMillis());
             }
-            customTile.updateTileState(tile);
+            customTile.updateTileState(tile, uid);
             customTile.refreshState();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 21aaa94..b50798e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
 import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractorImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -61,6 +63,11 @@
     ): InstalledTilesComponentRepository
 
     @Binds
+    abstract fun provideDisabledByPolicyInteractor(
+        impl: DisabledByPolicyInteractorImpl
+    ): DisabledByPolicyInteractor
+
+    @Binds
     @IntoMap
     @ClassKey(QSPipelineCoreStartable::class)
     abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 632aa63..38bbce4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -51,15 +51,15 @@
 
     protected final Map<String, Provider<QSTileImpl<?>>> mTileMap;
     private final Lazy<QSHost> mQsHostLazy;
-    private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
+    private final Provider<CustomTile.Factory> mCustomTileFactoryProvider;
 
     @Inject
     public QSFactoryImpl(
             Lazy<QSHost> qsHostLazy,
-            Provider<CustomTile.Builder> customTileBuilderProvider,
+            Provider<CustomTile.Factory> customTileFactoryProvider,
             Map<String, Provider<QSTileImpl<?>>> tileMap) {
         mQsHostLazy = qsHostLazy;
-        mCustomTileBuilderProvider = customTileBuilderProvider;
+        mCustomTileFactoryProvider = customTileFactoryProvider;
         mTileMap = tileMap;
     }
 
@@ -87,7 +87,7 @@
         // Custom tiles
         if (tileSpec.startsWith(CustomTile.PREFIX)) {
             return CustomTile.create(
-                    mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
+                    mCustomTileFactoryProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
         }
 
         // Broken tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
index dc9e115..9d10072 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.actions
 
 import android.content.Intent
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
new file mode 100644
index 0000000..056f967
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Provides restrictions data for the tiles. This is used in
+ * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is
+ * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy].
+ */
+interface DisabledByPolicyInteractor {
+
+    /**
+     * Checks if the tile is restricted by the policy for a specific user. Pass the result to the
+     * [handlePolicyResult] to let the user know that the tile is disable by the admin.
+     */
+    suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult
+
+    /**
+     * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this
+     * method. No further handling is required and the input event can be skipped at this point.
+     *
+     * Returns false when [policyResult] is [PolicyResult.TileEnabled] and this method has done
+     * nothing.
+     */
+    fun handlePolicyResult(policyResult: PolicyResult): Boolean
+
+    sealed interface PolicyResult {
+        /** Tile has no policy restrictions. */
+        data object TileEnabled : PolicyResult
+
+        /**
+         * Tile is disabled by policy. Pass this to [DisabledByPolicyInteractor.handlePolicyResult]
+         * to show the user info screen using
+         * [RestrictedLockUtils.getShowAdminSupportDetailsIntent].
+         */
+        data class TileDisabled(val admin: EnforcedAdmin) : PolicyResult
+    }
+}
+
+@SysUISingleton
+class DisabledByPolicyInteractorImpl
+@Inject
+constructor(
+    private val context: Context,
+    private val activityStarter: ActivityStarter,
+    private val restrictedLockProxy: RestrictedLockProxy,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : DisabledByPolicyInteractor {
+
+    override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult =
+        withContext(backgroundDispatcher) {
+            val admin: EnforcedAdmin =
+                restrictedLockProxy.getEnforcedAdmin(userId, userRestriction)
+                    ?: return@withContext PolicyResult.TileEnabled
+
+            return@withContext if (
+                !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction)
+            ) {
+                PolicyResult.TileDisabled(admin)
+            } else {
+                PolicyResult.TileEnabled
+            }
+        }
+
+    override fun handlePolicyResult(policyResult: PolicyResult): Boolean =
+        when (policyResult) {
+            is PolicyResult.TileEnabled -> false
+            is PolicyResult.TileDisabled -> {
+                val intent =
+                    RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+                        context,
+                        policyResult.admin
+                    )
+                activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+                true
+            }
+        }
+}
+
+/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */
+@VisibleForTesting
+class RestrictedLockProxy @Inject constructor(private val context: Context) {
+
+    @WorkerThread
+    fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean =
+        RestrictedLockUtilsInternal.hasBaseUserRestriction(
+            context,
+            userRestriction,
+            userId,
+        )
+
+    @WorkerThread
+    fun getEnforcedAdmin(userId: Int, userRestriction: String?): EnforcedAdmin? =
+        RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+            context,
+            userRestriction,
+            userId,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
index 1a03481..7a22e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
index 8289704..0aa6b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 data class QSTileDataRequest(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
index d6c9705..2bc6643 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import androidx.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 8569fc7..14fc639 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import android.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
index ed7ec8e..ffe38dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index bb4de80..58a335e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -1,8 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.viewmodel
 
 import androidx.annotation.CallSuper
 import androidx.annotation.VisibleForTesting
 import com.android.internal.util.Preconditions
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -10,10 +28,13 @@
 import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.util.kotlin.sample
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
@@ -25,6 +46,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -37,40 +59,35 @@
  * Provides a hassle-free way to implement new tiles according to current System UI architecture
  * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
  * [QSTileLifecycle.ALIVE] state.
+ *
+ * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
  */
-abstract class BaseQSTileViewModel<DATA_TYPE>
+class BaseQSTileViewModel<DATA_TYPE>
 @VisibleForTesting
 constructor(
-    final override val config: QSTileConfig,
+    override val config: QSTileConfig,
     private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
     private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
     private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
+    private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
     private val backgroundDispatcher: CoroutineDispatcher,
     private val tileScope: CoroutineScope,
 ) : QSTileViewModel {
 
-    /**
-     * @param config contains all the static information (like TileSpec) about the tile.
-     * @param userActionInteractor encapsulates user input processing logic. Use it to start
-     *   activities, show dialogs or otherwise update the tile state.
-     * @param tileDataInteractor provides [DATA_TYPE] and its availability.
-     * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call
-     *   interactors methods. This should likely to be @Background CoroutineDispatcher.
-     * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer.
-     *   It's called in [backgroundDispatcher], so it's safe to perform long running operations
-     *   there.
-     */
+    @AssistedInject
     constructor(
-        config: QSTileConfig,
-        userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
-        tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
-        mapper: QSTileDataToStateMapper<DATA_TYPE>,
-        backgroundDispatcher: CoroutineDispatcher,
+        @Assisted config: QSTileConfig,
+        @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+        @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+        @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
+        disabledByPolicyInteractor: DisabledByPolicyInteractor,
+        @Background backgroundDispatcher: CoroutineDispatcher,
     ) : this(
         config,
         userActionInteractor,
         tileDataInteractor,
         mapper,
+        disabledByPolicyInteractor,
         backgroundDispatcher,
         CoroutineScope(SupervisorJob())
     )
@@ -145,7 +162,7 @@
         userIds
             .flatMapLatest { userId ->
                 merge(
-                        userInputFlow(),
+                        userInputFlow(userId),
                         forceUpdates.map { StateUpdateTrigger.ForceUpdate },
                     )
                     .onStart { emit(StateUpdateTrigger.InitialRequest) }
@@ -172,14 +189,41 @@
                 replay = 1, // we only care about the most recent value
             )
 
-    private fun userInputFlow(): Flow<StateUpdateTrigger> {
+    private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
         data class StateWithData<T>(val state: QSTileState, val data: T)
 
+        return when (config.policy) {
+            is QSTilePolicy.NoRestrictions -> userInputs
+            is QSTilePolicy.Restricted ->
+                userInputs.filter {
+                    val result =
+                        disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
+                    !disabledByPolicyInteractor.handlePolicyResult(result)
+                }
         // Skip the input until there is some data
-        return userInputs.sample(
-            state.combine(tileData) { state, data -> StateWithData(state, data) }
-        ) { input, stateWithData ->
+        }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
+            input,
+            stateWithData ->
             StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
         }
     }
+
+    interface Factory<T> {
+
+        /**
+         * @param config contains all the static information (like TileSpec) about the tile.
+         * @param userActionInteractor encapsulates user input processing logic. Use it to start
+         *   activities, show dialogs or otherwise update the tile state.
+         * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+         * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
+         *   layer. It's called in [backgroundDispatcher], so it's safe to perform long running
+         *   operations there.
+         */
+        fun create(
+            config: QSTileConfig,
+            userActionInteractor: QSTileUserActionInteractor<T>,
+            tileDataInteractor: QSTileDataInteractor<T>,
+            mapper: QSTileDataToStateMapper<T>,
+        ): BaseQSTileViewModel<T>
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 3fedbfc..d0809c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.di
 
 import com.android.systemui.dagger.SysUISingleton
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 019d3c0..1a6cf99 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
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 import androidx.annotation.StringRes
@@ -10,4 +26,19 @@
     val tileIcon: Icon,
     @StringRes val tileLabelRes: Int,
     val instanceId: InstanceId,
+    val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
 )
+
+/** Represents policy restrictions that may be imposed on the tile. */
+sealed interface QSTilePolicy {
+    /** Tile has no policy restrictions */
+    data object NoRestrictions : QSTilePolicy
+
+    /**
+     * Tile might be disabled by policy. [userRestriction] is usually a constant from
+     * [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE].
+     * [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used
+     * to resolve this and show user a message when needed.
+     */
+    data class Restricted(val userRestriction: String) : QSTilePolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
index 1d5c1bc..6d7c576 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 enum class QSTileLifecycle {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index dc5c690..0ccde74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.service.quicksettings.Tile
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index 0b232c2..a145042 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.view.View
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index d66d0a1..e5cb7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 import kotlinx.coroutines.flow.SharedFlow
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index d4bdb77..f6299e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 722d366..a3499bd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.model.SysUiState
@@ -63,6 +64,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val sceneInteractor: SceneInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: SceneContainerFlags,
@@ -119,7 +121,7 @@
     /** Switches between scenes based on ever-changing application state. */
     private fun automaticallySwitchScenes() {
         applicationScope.launch {
-            authenticationInteractor.isUnlocked
+            deviceEntryInteractor.isUnlocked
                 .mapNotNull { isUnlocked ->
                     val renderedScenes =
                         when (val transitionState = sceneInteractor.transitionState.value) {
@@ -130,7 +132,6 @@
                                     transitionState.toScene,
                                 )
                         }
-                    val isBypassEnabled = authenticationInteractor.isBypassEnabled()
                     when {
                         isUnlocked ->
                             when {
@@ -141,7 +142,7 @@
                                 // When the device becomes unlocked in Lockscreen, go to Gone if
                                 // bypass is enabled.
                                 renderedScenes.contains(SceneKey.Lockscreen) ->
-                                    if (isBypassEnabled) {
+                                    if (deviceEntryInteractor.isBypassEnabled()) {
                                         SceneKey.Gone to
                                             "device unlocked in Lockscreen scene with bypass"
                                     } else {
@@ -191,7 +192,7 @@
                         }
                         WakefulnessState.STARTING_TO_WAKE -> {
                             val authMethod = authenticationInteractor.getAuthenticationMethod()
-                            val isUnlocked = authenticationInteractor.isUnlocked.value
+                            val isUnlocked = deviceEntryInteractor.isUnlocked.value
                             when {
                                 authMethod == AuthenticationMethodModel.None -> {
                                     switchToScene(
@@ -241,7 +242,7 @@
     /** Collects and reports signals into the falsing system. */
     private fun collectFalsingSignals() {
         applicationScope.launch {
-            authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+            deviceEntryInteractor.isDeviceEntered.collect { isLockscreenDismissed ->
                 if (isLockscreenDismissed) {
                     falsingCollector.onSuccessfulUnlock()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d5934e6..1038c67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -114,7 +114,6 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
@@ -163,6 +162,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -393,7 +393,6 @@
     private TrackingStartedListener mTrackingStartedListener;
     private OpenCloseListener mOpenCloseListener;
     private GestureRecorder mGestureRecorder;
-    private boolean mPanelExpanded;
 
     private boolean mKeyguardQsUserSwitchEnabled;
     private boolean mKeyguardUserSwitcherEnabled;
@@ -1321,7 +1320,7 @@
 
         // when we switch between split shade and regular shade we want to enforce setting qs to
         // the default state: expanded for split shade and collapsed otherwise
-        if (!isKeyguardShowing() && mPanelExpanded) {
+        if (!isKeyguardShowing() && isPanelExpanded()) {
             mQsController.setExpanded(mSplitShadeEnabled);
         }
         if (isKeyguardShowing() && mQsController.getExpanded() && mSplitShadeEnabled) {
@@ -2630,10 +2629,9 @@
 
     private void updatePanelExpanded() {
         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
-        if (mPanelExpanded != isExpanded) {
-            mPanelExpanded = isExpanded;
+        if (isPanelExpanded() != isExpanded) {
+            setExpandedOrAwaitingInputTransfer(isExpanded);
             updateSystemUiStateFlags();
-            mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(mPanelExpanded);
             mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
             if (!isExpanded) {
                 mQsController.closeQsCustomizer();
@@ -2641,9 +2639,13 @@
         }
     }
 
+    private void setExpandedOrAwaitingInputTransfer(boolean expandedOrAwaitingInputTransfer) {
+        mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expandedOrAwaitingInputTransfer);
+    }
+
     @Override
     public boolean isPanelExpanded() {
-        return mPanelExpanded;
+        return mShadeRepository.getLegacyExpandedOrAwaitingInputTransfer().getValue();
     }
 
     private int calculatePanelHeightShade() {
@@ -3392,7 +3394,7 @@
         ipw.print("mMaxAllowedKeyguardNotifications=");
         ipw.println(mMaxAllowedKeyguardNotifications);
         ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
-        ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded);
+        ipw.print("isPanelExpanded()="); ipw.println(isPanelExpanded());
         ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
         ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
         ipw.print("mDozing="); ipw.println(mDozing);
@@ -3606,7 +3608,7 @@
                     + isFullyExpanded() + " inQs=" + mQsController.getExpanded());
         }
         mSysUiState
-                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, mPanelExpanded)
+                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, isPanelExpanded())
                 .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
                         isFullyExpanded() && !mQsController.getExpanded())
                 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index c803e6f..6f5e41f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.IActivityManager;
 import android.content.Context;
@@ -48,7 +49,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
@@ -58,7 +58,9 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -71,6 +73,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
@@ -108,6 +112,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final Executor mBackgroundExecutor;
     private final AuthController mAuthController;
+    private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
@@ -151,6 +156,7 @@
             ScreenOffAnimationController screenOffAnimationController,
             AuthController authController,
             ShadeExpansionStateManager shadeExpansionStateManager,
+            Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeWindowLogger logger) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -171,12 +177,12 @@
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
+        mShadeInteractorLazy = shadeInteractorLazy;
         ((SysuiStatusBarStateController) statusBarStateController)
                 .addCallback(mStateListener,
                         SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
         configurationController.addCallback(this);
         shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
-        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         float desiredPreferredRefreshRate = context.getResources()
                 .getInteger(R.integer.config_keyguardRefreshRate);
@@ -224,9 +230,9 @@
     }
 
     @VisibleForTesting
-    void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mCurrentState.panelExpanded != isExpanded) {
-            mCurrentState.panelExpanded = isExpanded;
+    void onShadeOrQsExpanded(Boolean isExpanded) {
+        if (mCurrentState.shadeOrQsExpanded != isExpanded) {
+            mCurrentState.shadeOrQsExpanded = isExpanded;
             apply(mCurrentState);
         }
     }
@@ -289,6 +295,11 @@
     public void fetchWindowRootView() {
         WindowRootViewComponent component = mWindowRootViewComponentFactory.create();
         mWindowRootView = component.getWindowRootView();
+        collectFlow(
+                mWindowRootView,
+                mShadeInteractorLazy.get().isAnyExpanded(),
+                this::onShadeOrQsExpanded
+        );
     }
 
     @Override
@@ -384,7 +395,7 @@
     }
 
     private void applyFocusableFlag(NotificationShadeWindowState state) {
-        boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+        boolean panelFocusable = state.notificationShadeFocusable && state.shadeOrQsExpanded;
         if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
                 || ENABLE_REMOTE_INPUT && state.remoteInputActive
                 // Make the panel focusable if we're doing the screen off animation, since the light
@@ -408,7 +419,7 @@
     }
 
     private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
-        if (state.panelExpanded || state.bouncerShowing
+        if (state.shadeOrQsExpanded || state.bouncerShowing
                 || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
         } else {
@@ -544,7 +555,7 @@
                 state.keyguardOccluded,
                 state.keyguardNeedsInput,
                 state.panelVisible,
-                state.panelExpanded,
+                state.shadeOrQsExpanded,
                 state.notificationShadeFocusable,
                 state.bouncerShowing,
                 state.keyguardFadingAway,
@@ -582,7 +593,7 @@
                     mCurrentState.keyguardGoingAway,
                     mCurrentState.bouncerShowing,
                     mCurrentState.dozing,
-                    mCurrentState.panelExpanded,
+                    mCurrentState.shadeOrQsExpanded,
                     mCurrentState.dreaming);
         }
     }
@@ -833,7 +844,7 @@
      */
     @Override
     public boolean getPanelExpanded() {
-        return mCurrentState.panelExpanded;
+        return mCurrentState.shadeOrQsExpanded;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index e3010ca..fbe164a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -32,7 +32,7 @@
     @JvmField var keyguardNeedsInput: Boolean = false,
     @JvmField var panelVisible: Boolean = false,
     /** shade panel is expanded (expansion fraction > 0) */
-    @JvmField var panelExpanded: Boolean = false,
+    @JvmField var shadeOrQsExpanded: Boolean = false,
     @JvmField var notificationShadeFocusable: Boolean = false,
     @JvmField var bouncerShowing: Boolean = false,
     @JvmField var keyguardFadingAway: Boolean = false,
@@ -70,7 +70,7 @@
             keyguardOccluded.toString(),
             keyguardNeedsInput.toString(),
             panelVisible.toString(),
-            panelExpanded.toString(),
+            shadeOrQsExpanded.toString(),
             notificationShadeFocusable.toString(),
             bouncerShowing.toString(),
             keyguardFadingAway.toString(),
@@ -137,7 +137,7 @@
                 this.keyguardOccluded = keyguardOccluded
                 this.keyguardNeedsInput = keyguardNeedsInput
                 this.panelVisible = panelVisible
-                this.panelExpanded = panelExpanded
+                this.shadeOrQsExpanded = panelExpanded
                 this.notificationShadeFocusable = notificationShadeFocusable
                 this.bouncerShowing = bouncerShowing
                 this.keyguardFadingAway = keyguardFadingAway
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 068d5a5..9c5a201 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,15 +34,15 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    authenticationInteractor: AuthenticationInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
     private val bouncerInteractor: BouncerInteractor,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
         combine(
-                authenticationInteractor.isUnlocked,
-                authenticationInteractor.canSwipeToDismiss,
+                deviceEntryInteractor.isUnlocked,
+                deviceEntryInteractor.canSwipeToEnter,
             ) { isUnlocked, canSwipeToDismiss ->
                 upDestinationSceneKey(
                     isUnlocked = isUnlocked,
@@ -54,8 +54,8 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     upDestinationSceneKey(
-                        isUnlocked = authenticationInteractor.isUnlocked.value,
-                        canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
+                        isUnlocked = deviceEntryInteractor.isUnlocked.value,
+                        canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
                     ),
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 93bb435..664103f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -442,6 +442,7 @@
          * @see IStatusBar#requestAddTile
          */
         default void requestAddTile(
+                int callingUid,
                 @NonNull ComponentName componentName,
                 @NonNull CharSequence appName,
                 @NonNull CharSequence label,
@@ -1314,6 +1315,7 @@
 
     @Override
     public void requestAddTile(
+            int callingUid,
             @NonNull ComponentName componentName,
             @NonNull CharSequence appName,
             @NonNull CharSequence label,
@@ -1326,6 +1328,7 @@
         args.arg3 = label;
         args.arg4 = icon;
         args.arg5 = callback;
+        args.arg6 = callingUid;
         mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget();
     }
 
@@ -1772,8 +1775,9 @@
                     CharSequence label = (CharSequence) args.arg3;
                     Icon icon = (Icon) args.arg4;
                     IAddTileResultCallback callback = (IAddTileResultCallback) args.arg5;
+                    int callingUid = (int) args.arg6;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).requestAddTile(
+                        mCallbacks.get(i).requestAddTile(callingUid,
                                 componentName, appName, label, icon, callback);
                     }
                     args.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index d908243..93c55de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -24,8 +24,6 @@
     String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
             = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
 
-    boolean shouldAllowLockscreenRemoteInput();
-
     /**
      * @param userId user Id
      * @return true if we re on a secure lock screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index ea5ca27..2147510 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,8 +49,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
@@ -63,14 +61,14 @@
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
  * contents, redacting notifications, and the lockscreen.
@@ -81,8 +79,6 @@
         NotificationLockscreenUserManager,
         StateListener {
     private static final String TAG = "LockscreenUserManager";
-    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
-
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final KeyguardStateController mKeyguardStateController;
     private final SecureSettings mSecureSettings;
@@ -103,9 +99,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
     private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
-    private final FeatureFlags mFeatureFlags;
     private boolean mShowLockscreenNotifications;
-    private boolean mAllowLockscreenRemoteInput;
     private LockPatternUtils mLockPatternUtils;
     protected KeyguardManager mKeyguardManager;
     private int mState = StatusBarState.SHADE;
@@ -181,22 +175,7 @@
     protected final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
-                public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    if (!mFeatureFlags.isEnabled(
-                            Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
-                        handleUserChange(newUser);
-                    }
-                }
-
-                @Override
                 public void onUserChanging(int newUser, @NonNull Context userContext) {
-                    if (mFeatureFlags.isEnabled(
-                            Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
-                        handleUserChange(newUser);
-                    }
-                }
-
-                private void handleUserChange(int newUser) {
                     mCurrentUserId = newUser;
                     updateCurrentProfilesCache();
 
@@ -239,8 +218,7 @@
             KeyguardStateController keyguardStateController,
             SecureSettings secureSettings,
             DumpManager dumpManager,
-            LockPatternUtils lockPatternUtils,
-            FeatureFlags featureFlags) {
+            LockPatternUtils lockPatternUtils) {
         mContext = context;
         mMainHandler = mainHandler;
         mDevicePolicyManager = devicePolicyManager;
@@ -258,7 +236,6 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mSecureSettings = secureSettings;
         mKeyguardStateController = keyguardStateController;
-        mFeatureFlags = featureFlags;
 
         dumpManager.registerDumpable(this);
     }
@@ -305,14 +282,6 @@
                 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
                 mSettingsObserver);
 
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            mContext.getContentResolver().registerContentObserver(
-                    mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
-                    false,
-                    mSettingsObserver,
-                    UserHandle.USER_ALL);
-        }
-
         mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
                 new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                 null /* handler */, UserHandle.ALL);
@@ -343,10 +312,6 @@
         return mShowLockscreenNotifications;
     }
 
-    public boolean shouldAllowLockscreenRemoteInput() {
-        return mAllowLockscreenRemoteInput;
-    }
-
     public boolean isCurrentProfile(int userId) {
         synchronized (mLock) {
             return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
@@ -357,10 +322,6 @@
         mShowLockscreenNotifications = show;
     }
 
-    private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
-        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
-    }
-
     protected void updateLockscreenNotificationSetting() {
         final boolean show = mSecureSettings.getIntForUser(
                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
@@ -372,19 +333,6 @@
                 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
 
         setShowLockscreenNotifications(show && allowedByDpm);
-
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            final boolean remoteInput = mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
-                    0,
-                    mCurrentUserId) != 0;
-            final boolean remoteInputDpm =
-                    (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
-            setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
-        } else {
-            setLockscreenAllowRemoteInput(false);
-        }
     }
 
     /**
@@ -472,7 +420,6 @@
             mUsersAllowingNotifications.append(userHandle, allowed);
             return allowed;
         }
-
         return mUsersAllowingNotifications.get(userHandle);
     }
 
@@ -639,37 +586,6 @@
         }
     }
 
-//    public void updatePublicMode() {
-//        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
-//        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
-//        // asking if the keyguard is showing. We still need to check it though because showing the
-//        // camera on the keyguard has a state of SHADE but the keyguard is still showing.
-//        final boolean showingKeyguard = mState != StatusBarState.SHADE
-//              || mKeyguardStateController.isShowing();
-//        final boolean devicePublic = showingKeyguard && isSecure(getCurrentUserId());
-//
-//
-//        // Look for public mode users. Users are considered public in either case of:
-//        //   - device keyguard is shown in secure mode;
-//        //   - profile is locked with a work challenge.
-//        SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
-//        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
-//            final int userId = currentProfiles.valueAt(i).id;
-//            boolean isProfilePublic = devicePublic;
-//            if (!devicePublic && userId != getCurrentUserId()) {
-//                // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge
-//                // due to a race condition where this code could be called before
-//                // TrustManagerService updates its internal records, resulting in an incorrect
-//                // state being cached in mLockscreenPublicMode. (b/35951989)
-//                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-//                        && isSecure(userId)) {
-//                    isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
-//                }
-//            }
-//            setLockscreenPublicMode(isProfilePublic, userId);
-//        }
-//    }
-
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
@@ -677,8 +593,6 @@
         pw.println(mCurrentUserId);
         pw.print("  mShowLockscreenNotifications=");
         pw.println(mShowLockscreenNotifications);
-        pw.print("  mAllowLockscreenRemoteInput=");
-        pw.println(mAllowLockscreenRemoteInput);
         pw.print("  mCurrentProfiles=");
         synchronized (mLock) {
             for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index befc64d..d4b6dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -482,9 +482,6 @@
 
     private boolean showBouncerForRemoteInput(View view, PendingIntent pendingIntent,
             ExpandableNotificationRow row) {
-        if (mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
-            return false;
-        }
 
         final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
index 10e7573..718c1c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "CtsNotificationTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
@@ -14,9 +11,6 @@
         },
         {
           "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
         }
       ]
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index d089252..64dfc6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -167,11 +167,7 @@
      * @see Vibrator#getPrimitiveDurations(int...)
      */
     public int[] getPrimitiveDurations(int... primitiveIds) {
-        if (!hasVibrator()) {
-            return new int[]{0};
-        } else {
-            return mVibrator.getPrimitiveDurations(primitiveIds);
-        }
+        return mVibrator.getPrimitiveDurations(primitiveIds);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index d1464ed..249c831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,13 +17,16 @@
 package com.android.systemui.statusbar.dagger
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.core.StatusBarInitializer
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 
 /**
  * A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -49,4 +52,15 @@
     @IntoMap
     @ClassKey(OngoingCallController::class)
     abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(LightBarController::class)
+    abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+
+    @Binds
+    @IntoSet
+    abstract fun statusBarInitializedListener(
+        statusBarModeRepository: StatusBarModeRepository,
+    ): StatusBarInitializer.OnStatusBarViewInitializedListener
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt
new file mode 100644
index 0000000..0cd31d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.model
+
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.statusbar.phone.BoundsPair
+
+/** Keeps track of various parameters coordinating the appearance of the status bar. */
+data class StatusBarAppearance(
+    /** The current mode of the status bar. */
+    val mode: StatusBarMode,
+    /** The current bounds of the status bar. */
+    val bounds: BoundsPair,
+    /**
+     * A list of appearance regions for the appearance of the status bar background. Used to
+     * determine the correct coloring of status bar icons to ensure contrast. See
+     * [com.android.systemui.statusbar.phone.LightBarController].
+     */
+    val appearanceRegions: List<AppearanceRegion>,
+    /**
+     * The navigation bar color as set by
+     * [com.android.systemui.statusbar.CommandQueue.onSystemBarAttributesChanged].
+     *
+     * TODO(b/277764509): This likely belongs in a "NavigationBarAppearance"-type class, not a
+     *   status bar class.
+     */
+    val navbarColorManagedByIme: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
new file mode 100644
index 0000000..747efe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.model
+
+import com.android.systemui.statusbar.phone.BarTransitions
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode
+
+/**
+ * The possible status bar modes.
+ *
+ * See the associated [BarTransitions] mode documentation for information about each of the modes
+ * and how they're used.
+ */
+enum class StatusBarMode {
+    /** Use a semi-transparent (aka translucent) background for the status bar. */
+    SEMI_TRANSPARENT,
+    /**
+     * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode
+     * can be requested by apps). See
+     * [com.android.systemui.statusbar.phone.LightsOutNotifController].
+     */
+    LIGHTS_OUT,
+    /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */
+    LIGHTS_OUT_TRANSPARENT,
+    /** Use an opaque background for the status bar. */
+    OPAQUE,
+    /** Use a transparent background for the status bar. */
+    TRANSPARENT;
+
+    /** Converts a [StatusBarMode] to its [BarTransitions] integer. */
+    @TransitionMode
+    fun toTransitionModeInt(): Int {
+        return when (this) {
+            SEMI_TRANSPARENT -> MODE_SEMI_TRANSPARENT
+            LIGHTS_OUT -> MODE_LIGHTS_OUT
+            LIGHTS_OUT_TRANSPARENT -> MODE_LIGHTS_OUT_TRANSPARENT
+            OPAQUE -> MODE_OPAQUE
+            TRANSPARENT -> MODE_TRANSPARENT
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 9d73071..2b05994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -16,8 +16,13 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import android.graphics.Rect
 import android.view.WindowInsets
 import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
+import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
+import android.view.WindowInsetsController.Appearance
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
 import com.android.systemui.CoreStartable
@@ -25,12 +30,22 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.data.model.StatusBarAppearance
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.BoundsPair
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import java.io.PrintWriter
 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.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -41,7 +56,7 @@
  * Note: These status bar modes are status bar *window* states that are sent to us from
  * WindowManager, not determined internally.
  */
-interface StatusBarModeRepository {
+interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
     /**
      * True if the status bar window is showing transiently and will disappear soon, and false
      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -62,6 +77,16 @@
     val isInFullscreenMode: StateFlow<Boolean>
 
     /**
+     * The current status bar appearance parameters.
+     *
+     * Null at system startup, but non-null once the first system callback has been received.
+     */
+    val statusBarAppearance: StateFlow<StatusBarAppearance?>
+
+    /** The current mode of the status bar. */
+    val statusBarMode: StateFlow<StatusBarMode>
+
+    /**
      * Requests for the status bar to be shown transiently.
      *
      * TODO(b/277764509): Don't allow [CentralSurfaces] to set the transient mode; have it
@@ -85,6 +110,8 @@
     @Application scope: CoroutineScope,
     @DisplayId thisDisplayId: Int,
     private val commandQueue: CommandQueue,
+    private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
+    ongoingCallRepository: OngoingCallRepository,
 ) : StatusBarModeRepository, CoreStartable {
 
     private val commandQueueCallback =
@@ -114,7 +141,7 @@
 
             override fun onSystemBarAttributesChanged(
                 displayId: Int,
-                @WindowInsetsController.Appearance appearance: Int,
+                @Appearance appearance: Int,
                 appearanceRegions: Array<AppearanceRegion>,
                 navbarColorManagedByIme: Boolean,
                 @WindowInsetsController.Behavior behavior: Int,
@@ -125,7 +152,11 @@
                 if (displayId != thisDisplayId) return
                 _originalStatusBarAttributes.value =
                     StatusBarAttributes(
+                        appearance,
+                        appearanceRegions.toList(),
+                        navbarColorManagedByIme,
                         requestedVisibleTypes,
+                        letterboxDetails.toList(),
                     )
             }
         }
@@ -139,6 +170,19 @@
 
     private val _originalStatusBarAttributes = MutableStateFlow<StatusBarAttributes?>(null)
 
+    private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect()))
+
+    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+        val statusBarBoundsProvider = component.boundsProvider
+        val listener =
+            object : StatusBarBoundsProvider.BoundsChangeListener {
+                override fun onStatusBarBoundsChanged(bounds: BoundsPair) {
+                    _statusBarBounds.value = bounds
+                }
+            }
+        statusBarBoundsProvider.addChangeListener(listener)
+    }
+
     override val isInFullscreenMode: StateFlow<Boolean> =
         _originalStatusBarAttributes
             .map { params ->
@@ -148,6 +192,89 @@
             }
             .stateIn(scope, SharingStarted.Eagerly, false)
 
+    /** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */
+    private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> =
+        combine(
+                _originalStatusBarAttributes,
+                _statusBarBounds,
+            ) { originalAttributes, statusBarBounds ->
+                if (originalAttributes == null) {
+                    null
+                } else {
+                    val (newAppearance, newAppearanceRegions) =
+                        modifyAppearanceIfNeeded(
+                            originalAttributes.appearance,
+                            originalAttributes.appearanceRegions,
+                            originalAttributes.letterboxDetails,
+                            statusBarBounds,
+                        )
+                    ModifiedStatusBarAttributes(
+                        newAppearance,
+                        newAppearanceRegions,
+                        originalAttributes.navbarColorManagedByIme,
+                        statusBarBounds,
+                    )
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
+
+    override val statusBarAppearance: StateFlow<StatusBarAppearance?> =
+        combine(
+                modifiedStatusBarAttributes,
+                isTransientShown,
+                isInFullscreenMode,
+                ongoingCallRepository.hasOngoingCall,
+            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
+                if (modifiedAttributes == null) {
+                    null
+                } else {
+                    val statusBarMode =
+                        toBarMode(
+                            modifiedAttributes.appearance,
+                            isTransientShown,
+                            isInFullscreenMode,
+                            hasOngoingCall,
+                        )
+                    StatusBarAppearance(
+                        statusBarMode,
+                        modifiedAttributes.statusBarBounds,
+                        modifiedAttributes.appearanceRegions,
+                        modifiedAttributes.navbarColorManagedByIme,
+                    )
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
+
+    override val statusBarMode: StateFlow<StatusBarMode> =
+        statusBarAppearance
+            .map { it?.mode ?: StatusBarMode.TRANSPARENT }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = StatusBarMode.TRANSPARENT)
+
+    private fun toBarMode(
+        appearance: Int,
+        isTransientShown: Boolean,
+        isInFullscreenMode: Boolean,
+        hasOngoingCall: Boolean,
+    ): StatusBarMode {
+        return when {
+            hasOngoingCall && isInFullscreenMode -> StatusBarMode.SEMI_TRANSPARENT
+            isTransientShown -> StatusBarMode.SEMI_TRANSPARENT
+            else -> appearance.toBarMode()
+        }
+    }
+
+    @Appearance
+    private fun Int.toBarMode(): StatusBarMode {
+        val lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS
+        return when {
+            this and lightsOutOpaque == lightsOutOpaque -> StatusBarMode.LIGHTS_OUT
+            this and APPEARANCE_LOW_PROFILE_BARS != 0 -> StatusBarMode.LIGHTS_OUT_TRANSPARENT
+            this and APPEARANCE_OPAQUE_STATUS_BARS != 0 -> StatusBarMode.OPAQUE
+            this and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS != 0 -> StatusBarMode.SEMI_TRANSPARENT
+            else -> StatusBarMode.TRANSPARENT
+        }
+    }
+
     override fun showTransient() {
         _isTransientShown.value = true
     }
@@ -156,11 +283,54 @@
         _isTransientShown.value = false
     }
 
+    private fun modifyAppearanceIfNeeded(
+        appearance: Int,
+        appearanceRegions: List<AppearanceRegion>,
+        letterboxDetails: List<LetterboxDetails>,
+        statusBarBounds: BoundsPair,
+    ): Pair<Int, List<AppearanceRegion>> =
+        if (shouldUseLetterboxAppearance(letterboxDetails)) {
+            val letterboxAppearance =
+                letterboxAppearanceCalculator.getLetterboxAppearance(
+                    appearance,
+                    appearanceRegions,
+                    letterboxDetails,
+                    statusBarBounds,
+                )
+            Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
+        } else {
+            Pair(appearance, appearanceRegions)
+        }
+
+    private fun shouldUseLetterboxAppearance(letterboxDetails: List<LetterboxDetails>) =
+        letterboxDetails.isNotEmpty()
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}")
+        pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}")
+        pw.println("statusBarMode: ${statusBarMode.value}")
+    }
+
     /**
      * Internal class keeping track of the raw status bar attributes received from the callback.
      * Should never be exposed.
      */
     private data class StatusBarAttributes(
+        @Appearance val appearance: Int,
+        val appearanceRegions: List<AppearanceRegion>,
+        val navbarColorManagedByIme: Boolean,
         @WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
+        val letterboxDetails: List<LetterboxDetails>,
+    )
+
+    /**
+     * Internal class keeping track of how [StatusBarAttributes] were transformed into new
+     * attributes based on letterboxing and other factors. Should never be exposed.
+     */
+    private data class ModifiedStatusBarAttributes(
+        @Appearance val appearance: Int,
+        val appearanceRegions: List<AppearanceRegion>,
+        val navbarColorManagedByIme: Boolean,
+        val statusBarBounds: BoundsPair,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 3e1f09f..90cba40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -290,10 +290,6 @@
 
     void acquireGestureWakeLock(long time);
 
-    boolean setAppearance(int appearance);
-
-    int getBarMode();
-
     void resendMessage(int msg);
 
     void resendMessage(Object msg);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f7ff39c..22b9298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -36,16 +36,11 @@
 import android.util.Slog;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
-import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsController.Appearance;
-import android.view.WindowInsetsController.Behavior;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.statusbar.LetterboxDetails;
-import com.android.internal.view.AppearanceRegion;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.res.R;
 import com.android.systemui.assist.AssistManager;
@@ -109,7 +104,6 @@
     private final UserTracker mUserTracker;
     private final boolean mVibrateOnOpening;
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
-    private final SystemBarAttributesListener mSystemBarAttributesListener;
     private final ActivityStarter mActivityStarter;
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final QuickSettingsController mQsController;
@@ -149,7 +143,6 @@
             Optional<Vibrator> vibratorOptional,
             DisableFlagsLogger disableFlagsLogger,
             @DisplayId int displayId,
-            SystemBarAttributesListener systemBarAttributesListener,
             Lazy<CameraLauncher> cameraLauncherLazy,
             UserTracker userTracker,
             QSHost qsHost,
@@ -187,7 +180,6 @@
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
                 mVibratorOptional, resources);
-        mSystemBarAttributesListener = systemBarAttributesListener;
         mActivityStarter = activityStarter;
     }
 
@@ -457,29 +449,6 @@
         mCentralSurfaces.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, running);
     }
 
-
-    @Override
-    public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
-            AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
-            LetterboxDetails[] letterboxDetails) {
-        if (displayId != mDisplayId) {
-            return;
-        }
-        // SystemBarAttributesListener should __always__ be the top-level listener for system bar
-        // attributes changed.
-        mSystemBarAttributesListener.onSystemBarAttributesChanged(
-                displayId,
-                appearance,
-                appearanceRegions,
-                navbarColorManagedByIme,
-                behavior,
-                requestedVisibleTypes,
-                packageName,
-                letterboxDetails
-        );
-    }
-
     @Override
     public void toggleKeyboardShortcutsMenu(int deviceId) {
         mCentralSurfaces.resendMessage(new CentralSurfaces.KeyboardShortcutsMessage(deviceId));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 3cb5e1f..7dc4b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -85,8 +85,6 @@
     override fun getRotation() = 0
     override fun setBarStateForTest(state: Int) {}
     override fun acquireGestureWakeLock(time: Long) {}
-    override fun setAppearance(appearance: Int) = false
-    override fun getBarMode() = 0
     override fun resendMessage(msg: Int) {}
     override fun resendMessage(msg: Any?) {}
     override fun setLastCameraLaunchSource(source: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 05beded..3e2f10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -21,9 +21,6 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
-import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
 
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
@@ -33,12 +30,6 @@
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -92,7 +83,6 @@
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
@@ -208,6 +198,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.data.model.StatusBarMode;
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -222,7 +213,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -329,21 +319,6 @@
     }
 
     @Override
-    public boolean setAppearance(int appearance) {
-        if (mAppearance != appearance) {
-            mAppearance = appearance;
-            return updateBarMode(barMode(isTransientShown(), appearance));
-        }
-
-        return false;
-    }
-
-    @Override
-    public int getBarMode() {
-        return mStatusBarMode;
-    }
-
-    @Override
     public void resendMessage(int msg) {
         mMessageRouter.cancelMessages(msg);
         mMessageRouter.sendMessage(msg);
@@ -464,7 +439,6 @@
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DemoModeController mDemoModeController;
     private final NotificationsController mNotificationsController;
-    private final OngoingCallController mOngoingCallController;
     private final StatusBarSignalPolicy mStatusBarSignalPolicy;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
@@ -497,9 +471,6 @@
     private final Provider<FingerprintManager> mFingerprintManager;
     private final ActivityStarter mActivityStarter;
 
-    /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
-    private @Appearance int mAppearance;
-
     private final DisplayMetrics mDisplayMetrics;
 
     // XXX: gesture research
@@ -554,7 +525,6 @@
     private final DelayableExecutor mMainExecutor;
 
     private int mInteractingWindows;
-    private @TransitionMode int mStatusBarMode;
 
     private final ViewMediatorCallback mKeyguardViewMediatorCallback;
     private final ScrimController mScrimController;
@@ -714,7 +684,6 @@
             BrightnessSliderController.Factory brightnessSliderFactory,
             ScreenOffAnimationController screenOffAnimationController,
             WallpaperController wallpaperController,
-            OngoingCallController ongoingCallController,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             FeatureFlags featureFlags,
@@ -820,7 +789,6 @@
         mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
         mWallpaperController = wallpaperController;
-        mOngoingCallController = ongoingCallController;
         mStatusBarSignalPolicy = statusBarSignalPolicy;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mFeatureFlags = featureFlags;
@@ -854,9 +822,6 @@
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         mActivityLaunchAnimator = activityLaunchAnimator;
 
-        // The status bar background may need updating when the ongoing call status changes.
-        mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode());
-
         // TODO(b/190746471): Find a better home for this.
         DateTimeView.setReceiverHandler(timeTickHandler);
 
@@ -1192,8 +1157,8 @@
         mJavaAdapter.alwaysCollectFlow(
                 mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged);
         mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.isInFullscreenMode(),
-                this::onStatusBarFullscreenChanged);
+                mStatusBarModeRepository.getStatusBarMode(),
+                this::updateBarMode);
 
         mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
         mCommandQueue.addCallback(mCommandQueueCallbacks);
@@ -1712,50 +1677,12 @@
         if (transientShown) {
             mNoAnimationOnNextBarModeChange = true;
         }
-        maybeUpdateBarMode();
     }
 
-    private void onStatusBarFullscreenChanged(boolean isWindowShown) {
-        maybeUpdateBarMode();
-    }
-
-    private void maybeUpdateBarMode() {
-        final int barMode = barMode(isTransientShown(), mAppearance);
-        if (updateBarMode(barMode)) {
-            mLightBarController.onStatusBarModeChanged(barMode);
-            updateBubblesVisibility();
-        }
-    }
-
-    private boolean updateBarMode(int barMode) {
-        if (mStatusBarMode != barMode) {
-            mStatusBarMode = barMode;
-            checkBarModes();
-            mAutoHideController.touchAutoHide();
-            return true;
-        }
-        return false;
-    }
-
-    private @TransitionMode int barMode(boolean isTransient, int appearance) {
-        boolean isFullscreen = mStatusBarModeRepository.isInFullscreenMode().getValue();
-        final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
-        if (mOngoingCallController.hasOngoingCall() && isFullscreen) {
-            // Force show the status bar if there's an ongoing call.
-            return MODE_SEMI_TRANSPARENT;
-        } else if (isTransient) {
-            return MODE_SEMI_TRANSPARENT;
-        } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
-            return MODE_LIGHTS_OUT;
-        } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) {
-            return MODE_LIGHTS_OUT_TRANSPARENT;
-        } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) {
-            return MODE_OPAQUE;
-        } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) {
-            return MODE_SEMI_TRANSPARENT;
-        } else {
-            return MODE_TRANSPARENT;
-        }
+    private void updateBarMode(StatusBarMode barMode) {
+        checkBarModes();
+        mAutoHideController.touchAutoHide();
+        updateBubblesVisibility();
     }
 
     @Override
@@ -1785,7 +1712,10 @@
     public void checkBarModes() {
         if (mDemoModeController.isInDemoMode()) return;
         if (mStatusBarTransitions != null) {
-            checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions);
+            checkBarMode(
+                    mStatusBarModeRepository.getStatusBarMode().getValue(),
+                    mStatusBarWindowState,
+                    mStatusBarTransitions);
         }
         mNavigationBarController.checkNavBarModes(mDisplayId);
         mNoAnimationOnNextBarModeChange = false;
@@ -1794,16 +1724,19 @@
     /** Temporarily hides Bubbles if the status bar is hidden. */
     @Override
     public void updateBubblesVisibility() {
+        StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
         mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
-                mStatusBarMode != MODE_LIGHTS_OUT
-                        && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT));
+                mode != StatusBarMode.LIGHTS_OUT
+                        && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT));
     }
 
-    void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
+    void checkBarMode(
+            StatusBarMode mode,
+            @WindowVisibleState int windowState,
             BarTransitions transitions) {
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
                 && windowState != WINDOW_STATE_HIDDEN;
-        transitions.transitionTo(mode, anim);
+        transitions.transitionTo(mode.toTransitionModeInt(), anim);
     }
 
     private void finishBarAnimations() {
@@ -1850,8 +1783,6 @@
         pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
         pw.print("  mStatusBarWindowState=");
         pw.println(windowStateToString(mStatusBarWindowState));
-        pw.print("  mStatusBarMode=");
-        pw.println(BarTransitions.modeToString(mStatusBarMode));
         pw.print("  mDozing="); pw.println(mDozing);
         pw.print("  mWallpaperSupported= "); pw.println(mWallpaperSupported);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index a61914a..231a8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.annotation.ColorInt
+import android.content.Context
 import android.graphics.Rect
 import android.view.InsetsFlags
 import android.view.ViewDebug
@@ -29,20 +30,17 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import java.io.PrintWriter
-import java.util.Arrays
 import javax.inject.Inject
 
-class LetterboxAppearance(
+data class LetterboxAppearance(
     @Appearance val appearance: Int,
-    val appearanceRegions: Array<AppearanceRegion>
+    val appearanceRegions: List<AppearanceRegion>,
 ) {
     override fun toString(): String {
         val appearanceString =
                 ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
-        return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+        return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
     }
 }
 
@@ -54,69 +52,81 @@
 class LetterboxAppearanceCalculator
 @Inject
 constructor(
-    private val lightBarController: LightBarController,
+    context: Context,
     dumpManager: DumpManager,
     private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
-) : OnStatusBarViewInitializedListener, Dumpable {
+) : Dumpable {
+
+    private val darkAppearanceIconColor = context.getColor(
+        // For a dark background status bar, use a *light* icon color.
+        com.android.settingslib.R.color.light_mode_icon_color_single_tone
+    )
+    private val lightAppearanceIconColor = context.getColor(
+        // For a light background status bar, use a *dark* icon color.
+        com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+    )
 
     init {
         dumpManager.registerCriticalDumpable(this)
     }
 
-    private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
-
     private var lastAppearance: Int? = null
-    private var lastAppearanceRegions: Array<AppearanceRegion>? = null
-    private var lastLetterboxes: Array<LetterboxDetails>? = null
+    private var lastAppearanceRegions: List<AppearanceRegion>? = null
+    private var lastLetterboxes: List<LetterboxDetails>? = null
     private var lastLetterboxAppearance: LetterboxAppearance? = null
 
     fun getLetterboxAppearance(
         @Appearance originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        letterboxes: Array<LetterboxDetails>
+        originalAppearanceRegions: List<AppearanceRegion>,
+        letterboxes: List<LetterboxDetails>,
+        statusBarBounds: BoundsPair,
     ): LetterboxAppearance {
         lastAppearance = originalAppearance
         lastAppearanceRegions = originalAppearanceRegions
         lastLetterboxes = letterboxes
         return getLetterboxAppearanceInternal(
-                letterboxes, originalAppearance, originalAppearanceRegions)
+                letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
             .also { lastLetterboxAppearance = it }
     }
 
     private fun getLetterboxAppearanceInternal(
-        letterboxes: Array<LetterboxDetails>,
+        letterboxes: List<LetterboxDetails>,
         originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>
+        originalAppearanceRegions: List<AppearanceRegion>,
+        statusBarBounds: BoundsPair,
     ): LetterboxAppearance {
-        if (isScrimNeeded(letterboxes)) {
+        if (isScrimNeeded(letterboxes, statusBarBounds)) {
             return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
         }
         val appearance = appearanceWithoutScrim(originalAppearance)
         val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
-        return LetterboxAppearance(appearance, appearanceRegions.toTypedArray())
+        return LetterboxAppearance(appearance, appearanceRegions)
     }
 
-    private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean {
+    private fun isScrimNeeded(
+        letterboxes: List<LetterboxDetails>,
+        statusBarBounds: BoundsPair,
+    ): Boolean {
         if (isOuterLetterboxMultiColored()) {
             return true
         }
         return letterboxes.any { letterbox ->
-            letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) ||
-                letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds())
+            letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.start) ||
+                letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.end)
         }
     }
 
     private fun getAppearanceRegions(
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        letterboxes: Array<LetterboxDetails>
+        originalAppearanceRegions: List<AppearanceRegion>,
+        letterboxes: List<LetterboxDetails>
     ): List<AppearanceRegion> {
         return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
             getAllOuterAppearanceRegions(letterboxes)
     }
 
     private fun sanitizeAppearanceRegions(
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        letterboxes: Array<LetterboxDetails>
+        originalAppearanceRegions: List<AppearanceRegion>,
+        letterboxes: List<LetterboxDetails>
     ): List<AppearanceRegion> =
         originalAppearanceRegions.map { appearanceRegion ->
             val matchingLetterbox =
@@ -134,7 +144,7 @@
 
     private fun originalAppearanceWithScrim(
         @Appearance originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>
+        originalAppearanceRegions: List<AppearanceRegion>
     ): LetterboxAppearance {
         return LetterboxAppearance(
             originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
@@ -146,7 +156,7 @@
         originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()
 
     private fun getAllOuterAppearanceRegions(
-        letterboxes: Array<LetterboxDetails>
+        letterboxes: List<LetterboxDetails>
     ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()
 
     private fun getOuterAppearanceRegions(
@@ -172,11 +182,9 @@
     private fun getOuterAppearance(): Int {
         val backgroundColor = outerLetterboxBackgroundColor()
         val darkAppearanceContrast =
-            ContrastColorUtil.calculateContrast(
-                lightBarController.darkAppearanceIconColor, backgroundColor)
+            ContrastColorUtil.calculateContrast(darkAppearanceIconColor, backgroundColor)
         val lightAppearanceContrast =
-            ContrastColorUtil.calculateContrast(
-                lightBarController.lightAppearanceIconColor, backgroundColor)
+            ContrastColorUtil.calculateContrast(lightAppearanceIconColor, backgroundColor)
         return if (lightAppearanceContrast > darkAppearanceContrast) {
             WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
         } else {
@@ -193,18 +201,6 @@
         return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
     }
 
-    private fun getEndSideIconsBounds(): Rect {
-        return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect()
-    }
-
-    private fun getStartSideIconBounds(): Rect {
-        return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect()
-    }
-
-    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
-        statusBarBoundsProvider = component.boundsProvider
-    }
-
     private fun Rect.overlapsWith(other: Rect): Boolean {
         if (this.contains(other) || other.contains(this)) {
             return false
@@ -216,8 +212,8 @@
         pw.println(
             """
            lastAppearance: ${lastAppearance?.toAppearanceString()}
-           lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
-           lastLetterboxes: ${Arrays.toString(lastLetterboxes)},
+           lastAppearanceRegion: $lastAppearanceRegions,
+           lastLetterboxes: $lastLetterboxes,
            lastLetterboxAppearance: $lastLetterboxAppearance
        """.trimIndent())
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
index 61377e2..2e3f0d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
@@ -18,12 +18,10 @@
 package com.android.systemui.statusbar.phone
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
 
 @Module
 abstract class LetterboxModule {
@@ -31,10 +29,4 @@
     @IntoMap
     @ClassKey(LetterboxBackgroundProvider::class)
     abstract fun bindFeature(impl: LetterboxBackgroundProvider): CoreStartable
-
-    @Binds
-    @IntoSet
-    abstract fun statusBarInitializedListener(
-        letterboxAppearanceCalculator: LetterboxAppearanceCalculator
-    ): StatusBarInitializer.OnStatusBarViewInitializedListener
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index c2dd059..4d3e2ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -22,7 +22,6 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
-import android.annotation.ColorInt;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
@@ -31,17 +30,22 @@
 import android.view.WindowInsetsController.Appearance;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -52,7 +56,8 @@
  * Controls how light status bar flag applies to the icons.
  */
 @SysUISingleton
-public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
+public class LightBarController implements
+        BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
 
     private static final String TAG = "LightBarController";
     private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -60,18 +65,19 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
+    private final JavaAdapter mJavaAdapter;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
+    private final StatusBarModeRepository mStatusBarModeRepository;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
     private @Appearance int mAppearance;
     private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
     private int mStatusBarMode;
+    private BoundsPair mStatusBarBounds = new BoundsPair(new Rect(), new Rect());
     private int mNavigationBarMode;
     private int mNavigationMode;
-    private final int mDarkIconColor;
-    private final int mLightIconColor;
 
     /**
      * Whether the navigation bar should be light factoring in already how much alpha the scrim has.
@@ -116,18 +122,18 @@
     @Inject
     public LightBarController(
             Context ctx,
+            JavaAdapter javaAdapter,
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
+            StatusBarModeRepository statusBarModeRepository,
             DumpManager dumpManager,
             DisplayTracker displayTracker) {
-        mDarkIconColor = ctx.getColor(
-                com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
-        mLightIconColor = ctx.getColor(
-                com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+        mJavaAdapter = javaAdapter;
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
         mBatteryController.addCallback(this);
+        mStatusBarModeRepository = statusBarModeRepository;
         mNavigationMode = navModeController.addListener((mode) -> {
             mNavigationMode = mode;
         });
@@ -137,14 +143,11 @@
         }
     }
 
-    @ColorInt
-    int getLightAppearanceIconColor() {
-        return mDarkIconColor;
-    }
-
-    @ColorInt
-    int getDarkAppearanceIconColor() {
-        return mLightIconColor;
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mStatusBarModeRepository.getStatusBarAppearance(),
+                this::onStatusBarAppearanceChanged);
     }
 
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -157,26 +160,48 @@
         mBiometricUnlockController = biometricUnlockController;
     }
 
-    void onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged,
-            int statusBarMode, boolean navbarColorManagedByIme) {
+    private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
+        if (params == null) {
+            return;
+        }
+        int newStatusBarMode = params.getMode().toTransitionModeInt();
+        boolean sbModeChanged = mStatusBarMode != newStatusBarMode;
+        mStatusBarMode = newStatusBarMode;
+
+        boolean sbBoundsChanged = !mStatusBarBounds.equals(params.getBounds());
+        mStatusBarBounds = params.getBounds();
+
+        onStatusBarAppearanceChanged(
+                params.getAppearanceRegions().toArray(new AppearanceRegion[0]),
+                sbModeChanged,
+                sbBoundsChanged,
+                params.getNavbarColorManagedByIme());
+    }
+
+    private void onStatusBarAppearanceChanged(
+            AppearanceRegion[] appearanceRegions,
+            boolean sbModeChanged,
+            boolean sbBoundsChanged,
+            boolean navbarColorManagedByIme) {
         final int numStacks = appearanceRegions.length;
         boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks;
         for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) {
             stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]);
         }
-        if (stackAppearancesChanged || sbModeChanged || mIsCustomizingForBackNav) {
+
+        if (stackAppearancesChanged
+                || sbModeChanged
+                // Be sure to re-draw when the status bar bounds have changed because the status bar
+                // icons may have moved to be part of a different appearance region. See b/301605450
+                || sbBoundsChanged
+                || mIsCustomizingForBackNav) {
             mAppearanceRegions = appearanceRegions;
-            onStatusBarModeChanged(statusBarMode);
+            updateStatus(mAppearanceRegions);
             mIsCustomizingForBackNav = false;
         }
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
-    void onStatusBarModeChanged(int newBarMode) {
-        mStatusBarMode = newBarMode;
-        updateStatus(mAppearanceRegions);
-    }
-
     public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
@@ -224,7 +249,10 @@
     }
 
     private void reevaluate() {
-        onStatusBarAppearanceChanged(mAppearanceRegions, true /* sbModeChange */, mStatusBarMode,
+        onStatusBarAppearanceChanged(
+                mAppearanceRegions,
+                /* sbModeChanged= */ true,
+                /* sbBoundsChanged= */ true,
                 mNavbarColorManagedByIme);
         onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
                 mNavigationBarMode, mNavbarColorManagedByIme);
@@ -444,31 +472,43 @@
      * Injectable factory for creating a {@link LightBarController}.
      */
     public static class Factory {
+        private final JavaAdapter mJavaAdapter;
         private final DarkIconDispatcher mDarkIconDispatcher;
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
+        private final StatusBarModeRepository mStatusBarModeRepository;
         private final DumpManager mDumpManager;
         private final DisplayTracker mDisplayTracker;
 
         @Inject
         public Factory(
+                JavaAdapter javaAdapter,
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
+                StatusBarModeRepository statusBarModeRepository,
                 DumpManager dumpManager,
                 DisplayTracker displayTracker) {
-
+            mJavaAdapter = javaAdapter;
             mDarkIconDispatcher = darkIconDispatcher;
             mBatteryController = batteryController;
             mNavModeController = navModeController;
+            mStatusBarModeRepository = statusBarModeRepository;
             mDumpManager = dumpManager;
             mDisplayTracker = displayTracker;
         }
 
         /** Create an {@link LightBarController} */
         public LightBarController create(Context context) {
-            return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
-                    mNavModeController, mDumpManager, mDisplayTracker);
+            return new LightBarController(
+                    context,
+                    mJavaAdapter,
+                    mDarkIconDispatcher,
+                    mBatteryController,
+                    mNavModeController,
+                    mStatusBarModeRepository,
+                    mDumpManager,
+                    mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
index f5ba399..00b08f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -22,22 +22,34 @@
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.util.ListenerSet
 import com.android.systemui.util.boundsOnScreen
 import javax.inject.Inject
 import javax.inject.Named
 
-/** Provides various bounds within the status bar. */
+/**
+ * Provides the bounds of the **content** on each side of the status bar.
+ *
+ * This is distinct from [StatusBarContentInsetsProvider], which provides the bounds of full status
+ * bar after accounting for system insets.
+ */
 @StatusBarFragmentScope
 class StatusBarBoundsProvider
 @Inject
 constructor(
-    private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>,
     @Named(START_SIDE_CONTENT) private val startSideContent: View,
     @Named(END_SIDE_CONTENT) private val endSideContent: View,
 ) : StatusBarFragmentComponent.Startable {
 
     interface BoundsChangeListener {
-        fun onStatusBarBoundsChanged()
+        fun onStatusBarBoundsChanged(bounds: BoundsPair)
+    }
+
+    private val changeListeners = ListenerSet<BoundsChangeListener>()
+
+    fun addChangeListener(listener: BoundsChangeListener) {
+        changeListeners.addIfAbsent(listener)
+        listener.onStatusBarBoundsChanged(previousBounds)
     }
 
     private var previousBounds =
@@ -48,7 +60,7 @@
             val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds)
             if (previousBounds != newBounds) {
                 previousBounds = newBounds
-                changeListeners.forEach { it.onStatusBarBoundsChanged() }
+                changeListeners.forEach { it.onStatusBarBoundsChanged(newBounds) }
             }
         }
 
@@ -89,4 +101,10 @@
         get() = startSideContent.boundsOnScreen
 }
 
-private data class BoundsPair(val start: Rect, val end: Rect)
+/**
+ * Stores bounds of the status content.
+ *
+ * @property start the bounds of the status bar content on the start side (clock & notif icons).
+ * @property end the bounds of the status bar content on the end side (system icons & battery).
+ */
+data class BoundsPair(val start: Rect, val end: Rect)
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 8d86d72..eedf35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.WindowInsets.Type.navigationBars;
+
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -66,7 +67,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
@@ -134,7 +134,7 @@
     // dranw its first frame.
     private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
 
-    private static String TAG = "StatusBarKeyguardViewManager";
+    private static final String TAG = "StatusBarKeyguardViewManager";
     private static final boolean DEBUG = false;
 
     protected final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
deleted file mode 100644
index 829577b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.view.InsetsFlags
-import android.view.ViewDebug
-import android.view.WindowInsets.Type.InsetsType
-import android.view.WindowInsetsController.Appearance
-import android.view.WindowInsetsController.Behavior
-import com.android.internal.statusbar.LetterboxDetails
-import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
-import javax.inject.Inject
-
-/**
- * Top-level listener of system attributes changed. This class is __always the first__ one to be
- * notified about changes.
- *
- * It is responsible for modifying any attributes if necessary, and then notifying the other
- * downstream listeners.
- */
-@SysUISingleton
-class SystemBarAttributesListener
-@Inject
-internal constructor(
-    private val centralSurfaces: CentralSurfaces,
-    private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
-    private val lightBarController: LightBarController,
-    dumpManager: DumpManager,
-) : Dumpable, StatusBarBoundsProvider.BoundsChangeListener {
-
-    private var lastLetterboxAppearance: LetterboxAppearance? = null
-    private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null
-
-    init {
-        dumpManager.registerCriticalDumpable(this)
-    }
-
-    override fun onStatusBarBoundsChanged() {
-        val params = lastSystemBarAttributesParams
-        if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) {
-            onSystemBarAttributesChanged(
-                params.displayId,
-                params.appearance,
-                params.appearanceRegionsArray,
-                params.navbarColorManagedByIme,
-                params.behavior,
-                params.requestedVisibleTypes,
-                params.packageName,
-                params.letterboxesArray)
-        }
-    }
-
-    fun onSystemBarAttributesChanged(
-            displayId: Int,
-            @Appearance originalAppearance: Int,
-            originalAppearanceRegions: Array<AppearanceRegion>,
-            navbarColorManagedByIme: Boolean,
-            @Behavior behavior: Int,
-            @InsetsType requestedVisibleTypes: Int,
-            packageName: String,
-            letterboxDetails: Array<LetterboxDetails>
-    ) {
-        lastSystemBarAttributesParams =
-            SystemBarAttributesParams(
-                displayId,
-                originalAppearance,
-                originalAppearanceRegions.toList(),
-                navbarColorManagedByIme,
-                behavior,
-                requestedVisibleTypes,
-                packageName,
-                letterboxDetails.toList())
-
-        val (appearance, appearanceRegions) =
-            modifyAppearanceIfNeeded(
-                originalAppearance, originalAppearanceRegions, letterboxDetails)
-
-        val barModeChanged = centralSurfaces.setAppearance(appearance)
-
-        lightBarController.onStatusBarAppearanceChanged(
-            appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme)
-
-        centralSurfaces.updateBubblesVisibility()
-    }
-
-    private fun modifyAppearanceIfNeeded(
-        appearance: Int,
-        appearanceRegions: Array<AppearanceRegion>,
-        letterboxDetails: Array<LetterboxDetails>
-    ): Pair<Int, Array<AppearanceRegion>> =
-        if (shouldUseLetterboxAppearance(letterboxDetails)) {
-            val letterboxAppearance =
-                letterboxAppearanceCalculator.getLetterboxAppearance(
-                    appearance, appearanceRegions, letterboxDetails)
-            lastLetterboxAppearance = letterboxAppearance
-            Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
-        } else {
-            lastLetterboxAppearance = null
-            Pair(appearance, appearanceRegions)
-        }
-
-    private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
-        letterboxDetails.isNotEmpty()
-
-    override fun dump(printWriter: PrintWriter, strings: Array<String>) {
-        printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
-        printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
-    }
-}
-
-/**
- * Keeps track of the parameters passed in
- * [SystemBarAttributesListener.onSystemBarAttributesChanged].
- */
-private data class SystemBarAttributesParams(
-        val displayId: Int,
-        @Appearance val appearance: Int,
-        val appearanceRegions: List<AppearanceRegion>,
-        val navbarColorManagedByIme: Boolean,
-        @Behavior val behavior: Int,
-        @InsetsType val requestedVisibleTypes: Int,
-        val packageName: String,
-        val letterboxes: List<LetterboxDetails>,
-) {
-    val letterboxesArray = letterboxes.toTypedArray()
-    val appearanceRegionsArray = appearanceRegions.toTypedArray()
-    override fun toString(): String {
-        val appearanceToString =
-                ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
-        return """SystemBarAttributesParams(
-            displayId=$displayId,
-            appearance=$appearanceToString,
-            appearanceRegions=$appearanceRegions,
-            navbarColorManagedByIme=$navbarColorManagedByIme,
-            behavior=$behavior,
-            requestedVisibleTypes=$requestedVisibleTypes,
-            packageName='$packageName',
-            letterboxes=$letterboxes,
-            letterboxesArray=${letterboxesArray.contentToString()},
-            appearanceRegionsArray=${appearanceRegionsArray.contentToString()}
-            )""".trimMargin()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 07c23d1..6ef877b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,22 +26,16 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
-import dagger.multibindings.Multibinds;
 
 import java.util.Optional;
-import java.util.Set;
 
 import javax.inject.Named;
 
@@ -159,14 +153,4 @@
     static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.heads_up_status_bar_view);
     }
-
-    /** */
-    @Multibinds
-    Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
-
-    /** */
-    @Binds
-    @IntoSet
-    StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
-            SystemBarAttributesListener systemBarAttributesListener);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 4b1e7a4..b0532ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.time.SystemClock
@@ -57,6 +58,7 @@
 class OngoingCallController @Inject constructor(
     @Application private val scope: CoroutineScope,
     private val context: Context,
+    private val ongoingCallRepository: OngoingCallRepository,
     private val notifCollection: CommonNotifCollection,
     private val systemClock: SystemClock,
     private val activityStarter: ActivityStarter,
@@ -207,7 +209,7 @@
                 statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(true)
             }
             updateGestureListening()
-            mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+            sendStateChangeEvent()
         } else {
             // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
             // will return false and we fall back to typical notification handling.
@@ -261,7 +263,7 @@
         tearDownChipView()
         statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
-        mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+        sendStateChangeEvent()
         uidObserver.unregister()
     }
 
@@ -288,6 +290,11 @@
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
     }
 
+    private fun sendStateChangeEvent() {
+        ongoingCallRepository.setHasOngoingCall(hasOngoingCall())
+        mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+    }
+
     private data class CallNotificationInfo(
         val key: String,
         val callStartTime: Long,
@@ -374,9 +381,7 @@
             if (oldIsCallAppVisible != isCallAppVisible) {
                 // Animations may be run as a result of the call's state change, so ensure
                 // the listener is notified on the main thread.
-                mainExecutor.execute {
-                    mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
-                }
+                mainExecutor.execute { sendStateChangeEvent() }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
new file mode 100644
index 0000000..da9c45a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Repository storing whether there's current an ongoing call notification.
+ *
+ * This class is used to break a dependency cycle between
+ * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
+ * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two
+ * classes both refer to this repository.
+ */
+@SysUISingleton
+class OngoingCallRepository @Inject constructor() {
+    private val _hasOngoingCall = MutableStateFlow(false)
+    /** True if there's currently an ongoing call notification and false otherwise. */
+    val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow()
+
+    /**
+     * Sets whether there's currently an ongoing call notification. Should only be set from
+     * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
+     */
+    fun setHasOngoingCall(hasOngoingCall: Boolean) {
+        _hasOngoingCall.value = hasOngoingCall
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index efa092b..250fe53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -22,11 +22,12 @@
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 
@@ -90,50 +91,56 @@
                             )}"
                         )
                     )
-                is WifiNetworkModel.Active -> {
-                    val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
-                    val contentDescription =
-                        ContentDescription.Loaded(
-                            if (model.isValidated) {
-                                (levelDesc)
-                            } else {
-                                "$levelDesc,${context.getString(NO_INTERNET)}"
-                            }
-                        )
-                    Visible(model.toIcon(showHotspotInfo), contentDescription)
-                }
+                is WifiNetworkModel.Active -> model.toIcon(showHotspotInfo, context)
             }
 
-        @DrawableRes
-        private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
-            return if (!showHotspotInfo) {
-                this.toBasicIcon()
+        private fun WifiNetworkModel.Active.toIcon(
+            showHotspotInfo: Boolean,
+            context: Context,
+        ): Visible {
+            return if (
+                !showHotspotInfo ||
+                    this.hotspotDeviceType == WifiNetworkModel.HotspotDeviceType.NONE
+            ) {
+                this.toBasicIcon(context)
             } else {
-                when (this.hotspotDeviceType) {
-                    WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
-                    WifiNetworkModel.HotspotDeviceType.TABLET ->
-                        com.android.settingslib.R.drawable.ic_hotspot_tablet
-                    WifiNetworkModel.HotspotDeviceType.LAPTOP ->
-                        com.android.settingslib.R.drawable.ic_hotspot_laptop
-                    WifiNetworkModel.HotspotDeviceType.WATCH ->
-                        com.android.settingslib.R.drawable.ic_hotspot_watch
-                    WifiNetworkModel.HotspotDeviceType.AUTO ->
-                        com.android.settingslib.R.drawable.ic_hotspot_auto
-                    // Use phone as the default drawable
-                    WifiNetworkModel.HotspotDeviceType.PHONE,
-                    WifiNetworkModel.HotspotDeviceType.UNKNOWN,
-                    WifiNetworkModel.HotspotDeviceType.INVALID ->
-                        com.android.settingslib.R.drawable.ic_hotspot_phone
-                }
+                val icon =
+                    when (this.hotspotDeviceType) {
+                        WifiNetworkModel.HotspotDeviceType.TABLET ->
+                            com.android.settingslib.R.drawable.ic_hotspot_tablet
+                        WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+                            com.android.settingslib.R.drawable.ic_hotspot_laptop
+                        WifiNetworkModel.HotspotDeviceType.WATCH ->
+                            com.android.settingslib.R.drawable.ic_hotspot_watch
+                        WifiNetworkModel.HotspotDeviceType.AUTO ->
+                            com.android.settingslib.R.drawable.ic_hotspot_auto
+                        // Use phone as the default drawable
+                        WifiNetworkModel.HotspotDeviceType.PHONE,
+                        WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+                        WifiNetworkModel.HotspotDeviceType.INVALID ->
+                            com.android.settingslib.R.drawable.ic_hotspot_phone
+                        WifiNetworkModel.HotspotDeviceType.NONE ->
+                            throw IllegalStateException("NONE checked earlier")
+                    }
+                Visible(
+                    icon,
+                    ContentDescription.Loaded(context.getString(WIFI_OTHER_DEVICE_CONNECTION)),
+                )
             }
         }
 
-        @DrawableRes
-        private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+        private fun WifiNetworkModel.Active.toBasicIcon(context: Context): Visible {
+            val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
             return if (this.isValidated) {
-                WifiIcons.WIFI_FULL_ICONS[this.level]
+                Visible(
+                    WifiIcons.WIFI_FULL_ICONS[this.level],
+                    ContentDescription.Loaded(levelDesc),
+                )
             } else {
-                WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+                Visible(
+                    WifiIcons.WIFI_NO_INTERNET_ICONS[this.level],
+                    ContentDescription.Loaded("$levelDesc,${context.getString(NO_INTERNET)}"),
+                )
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ae8128d..27f8121 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -67,7 +67,7 @@
     private final ToastLogger mToastLogger;
     @Nullable private ToastPresenter mPresenter;
     @Nullable private ITransientNotificationCallback mCallback;
-    private ToastOutAnimatorListener mToastOutAnimatorListener;
+    @VisibleForTesting ToastOutAnimatorListener mToastOutAnimatorListener;
 
     @VisibleForTesting SystemUIToast mToast;
     private int mOrientation = ORIENTATION_PORTRAIT;
@@ -172,7 +172,7 @@
         if (mToast.getOutAnimation() != null) {
             Animator animator = mToast.getOutAnimation();
             mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
-                    runnable);
+                    runnable, animator);
             animator.addListener(mToastOutAnimatorListener);
             animator.start();
         } else {
@@ -211,14 +211,17 @@
         final ToastPresenter mPrevPresenter;
         final ITransientNotificationCallback mPrevCallback;
         @Nullable Runnable mShowNextToastRunnable;
+        @NonNull private final Animator mAnimator;
 
         ToastOutAnimatorListener(
                 @NonNull ToastPresenter presenter,
                 @NonNull ITransientNotificationCallback callback,
-                @Nullable Runnable runnable) {
+                @Nullable Runnable runnable,
+                @NonNull Animator animator) {
             mPrevPresenter = presenter;
             mPrevCallback = callback;
             mShowNextToastRunnable = runnable;
+            mAnimator = animator;
         }
 
         void setShowNextToastRunnable(Runnable runnable) {
@@ -231,6 +234,8 @@
             if (mShowNextToastRunnable != null) {
                 mShowNextToastRunnable.run();
             }
+            mAnimator.removeListener(this);
+            mShowNextToastRunnable = null;
             mToastOutAnimatorListener = null;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f6649bd..d54843d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,10 +36,8 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -47,6 +45,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.classifier.FalsingA11yDelegate
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -54,6 +53,7 @@
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -157,7 +157,7 @@
     private lateinit var sceneTestUtils: SceneTestUtils
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
-    private lateinit var authenticationInteractor: AuthenticationInteractor
+    private lateinit var deviceEntryInteractor: DeviceEntryInteractor
     @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
 
@@ -229,10 +229,10 @@
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
-        authenticationInteractor =
-            sceneTestUtils.authenticationInteractor(
-                repository = sceneTestUtils.authenticationRepository(),
-                sceneInteractor = sceneInteractor
+        deviceEntryInteractor =
+            sceneTestUtils.deviceEntryInteractor(
+                authenticationInteractor = sceneTestUtils.authenticationInteractor(),
+                sceneInteractor = sceneInteractor,
             )
 
         underTest =
@@ -268,7 +268,7 @@
                 keyguardTransitionInteractor,
                 primaryBouncerInteractor,
             ) {
-                authenticationInteractor
+                deviceEntryInteractor
             }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 187f098..b8d2bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -88,9 +88,9 @@
 import androidx.test.filters.LargeTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.util.leak.ReferenceTestUtils;
 import com.android.systemui.util.settings.SecureSettings;
@@ -121,6 +121,9 @@
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+    // The duration couldn't too short, otherwise the animation check on bounce effect
+    // won't work in expectation. (b/299537784)
+    private static final int BOUNCE_EFFECT_DURATION_MS = 2000;
     private static final long ANIMATION_DURATION_MS = 300;
     private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
     @Mock
@@ -205,6 +208,7 @@
                         mSysUiState,
                         () -> mWindowSessionSpy,
                         mSecureSettings);
+        mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS);
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index d3a2a73..0283382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -74,7 +74,6 @@
                 getSecurityMode = getSecurityMode,
                 backgroundDispatcher = testUtils.testDispatcher,
                 userRepository = userRepository,
-                keyguardRepository = testUtils.keyguardRepository,
                 lockPatternUtils = lockPatternUtils,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 874053a..a102890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -20,15 +20,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -47,13 +44,7 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val repository: AuthenticationRepository = utils.authenticationRepository()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val underTest =
-        utils.authenticationInteractor(
-            repository = repository,
-            sceneInteractor = sceneInteractor,
-        )
+    private val underTest = utils.authenticationInteractor()
 
     @Test
     fun authenticationMethod() =
@@ -79,10 +70,10 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(true)
-            }
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.None
+            )
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
 
             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
             assertThat(underTest.getAuthenticationMethod())
@@ -95,10 +86,10 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(false)
-            }
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.None
+            )
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(false)
 
             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
             assertThat(underTest.getAuthenticationMethod())
@@ -106,130 +97,6 @@
         }
 
     @Test
-    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(false)
-                // Toggle isUnlocked, twice.
-                //
-                // This is done because the underTest.isUnlocked flow doesn't receive values from
-                // just changing the state above; the actual isUnlocked state needs to change to
-                // cause the logic under test to "pick up" the current state again.
-                //
-                // It is done twice to make sure that we don't actually change the isUnlocked state
-                // from what it originally was.
-                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
-                runCurrent()
-                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
-                runCurrent()
-            }
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(true)
-            }
-
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(true)
-            }
-            switchToScene(SceneKey.Lockscreen)
-
-            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
-            assertThat(canSwipeToDismiss).isTrue()
-        }
-
-    @Test
-    fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
-                setLockscreenEnabled(true)
-            }
-            switchToScene(SceneKey.Lockscreen)
-
-            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
-            assertThat(canSwipeToDismiss).isFalse()
-        }
-
-    @Test
-    fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-                setLockscreenEnabled(true)
-            }
-            switchToScene(SceneKey.Lockscreen)
-            switchToScene(SceneKey.Gone)
-
-            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
-            assertThat(canSwipeToDismiss).isFalse()
-        }
-
-    @Test
-    fun isAuthenticationRequired_lockedAndSecured_true() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setUnlocked(false)
-                runCurrent()
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
-            }
-
-            assertThat(underTest.isAuthenticationRequired()).isTrue()
-        }
-
-    @Test
-    fun isAuthenticationRequired_lockedAndNotSecured_false() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setUnlocked(false)
-                runCurrent()
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-            }
-
-            assertThat(underTest.isAuthenticationRequired()).isFalse()
-        }
-
-    @Test
-    fun isAuthenticationRequired_unlockedAndSecured_false() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setUnlocked(true)
-                runCurrent()
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
-            }
-
-            assertThat(underTest.isAuthenticationRequired()).isFalse()
-        }
-
-    @Test
-    fun isAuthenticationRequired_unlockedAndNotSecured_false() =
-        testScope.runTest {
-            utils.authenticationRepository.apply {
-                setUnlocked(true)
-                runCurrent()
-                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
-            }
-
-            assertThat(underTest.isAuthenticationRequired()).isFalse()
-        }
-
-    @Test
     fun authenticate_withCorrectPin_returnsTrue() =
         testScope.runTest {
             val isThrottled by collectLastValue(underTest.isThrottled)
@@ -366,7 +233,6 @@
     @Test
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
                 setAutoConfirmEnabled(true)
@@ -378,13 +244,13 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.FAILED)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             assertThat(isUnlocked).isFalse()
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
                 setAutoConfirmEnabled(true)
@@ -396,13 +262,13 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.FAILED)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             assertThat(isUnlocked).isFalse()
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
                 setAutoConfirmEnabled(true)
@@ -414,13 +280,13 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             assertThat(isUnlocked).isTrue()
         }
 
     @Test
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
                 setAutoConfirmEnabled(false)
@@ -432,26 +298,27 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SKIPPED)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             assertThat(isUnlocked).isFalse()
         }
 
     @Test
     fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.setAuthenticationMethod(
                 DataLayerAuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
                 .isEqualTo(AuthenticationResult.SKIPPED)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             assertThat(isUnlocked).isFalse()
         }
 
     @Test
     fun throttling() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             val throttling by collectLastValue(underTest.throttling)
             val isThrottled by collectLastValue(underTest.isThrottled)
             utils.authenticationRepository.setAuthenticationMethod(
@@ -462,7 +329,7 @@
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
 
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             assertThat(isUnlocked).isFalse()
             assertThat(isThrottled).isFalse()
             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
@@ -605,8 +472,4 @@
 
             assertThat(hintedPinLength).isNull()
         }
-
-    private fun switchToScene(sceneKey: SceneKey) {
-        sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 92c8a39..a9ba36a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -47,13 +47,16 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
+    private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
     private val underTest =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
@@ -76,7 +79,7 @@
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -111,7 +114,7 @@
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -148,7 +151,7 @@
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.clearMessage()
@@ -180,7 +183,7 @@
                 AuthenticationMethodModel.Password
             )
             runCurrent()
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -215,7 +218,7 @@
                 AuthenticationMethodModel.Pattern
             )
             runCurrent()
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -268,7 +271,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.showOrUnlockDevice()
@@ -281,8 +284,8 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            utils.deviceEntryRepository.setUnlocked(false)
 
             underTest.showOrUnlockDevice()
 
@@ -298,7 +301,7 @@
                 AuthenticationMethodModel.Password
             )
             runCurrent()
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
             underTest.showOrUnlockDevice(customMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f7dde0..b5177e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -37,17 +37,20 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            utils.authenticationRepository(),
-        )
+    private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
             viewModelScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
+                    deviceEntryInteractor = deviceEntryInteractor,
                     authenticationInteractor = authenticationInteractor,
                     sceneInteractor = sceneInteractor,
                 ),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index da2534d6..b75355a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -44,12 +44,15 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository,
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = utils.sceneInteractor(),
         )
     private val bouncerInteractor =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = utils.sceneInteractor(),
         )
@@ -223,6 +226,8 @@
                     DataLayerAuthenticationMethodModel.Pattern
             }
         )
-        setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
+        utils.deviceEntryRepository.setInsecureLockscreenEnabled(
+            model !is DomainLayerAuthenticationMethodModel.None
+        )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c1b3354..0926399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -44,13 +44,16 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
+    private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = utils.sceneInteractor(),
+        )
     private val bouncerInteractor =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
@@ -139,7 +142,7 @@
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -207,7 +210,7 @@
 
     private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
-        utils.authenticationRepository.setUnlocked(false)
+        utils.deviceEntryRepository.setUnlocked(false)
         sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
         sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index bf109d9..2e7c9aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -47,13 +47,16 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
+    private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = utils.sceneInteractor(),
+        )
     private val bouncerInteractor =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
@@ -378,7 +381,7 @@
 
     private fun TestScope.lockDeviceAndOpenPatternBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
-        utils.authenticationRepository.setUnlocked(false)
+        utils.deviceEntryRepository.setUnlocked(false)
         sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
         sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
         assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2576204..255bbe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -47,12 +47,15 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = utils.sceneInteractor(),
         )
     private val bouncerInteractor =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
@@ -81,7 +84,7 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
 
@@ -103,7 +106,7 @@
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
 
@@ -358,7 +361,7 @@
 
     private fun TestScope.lockDeviceAndOpenPinBouncer() {
         utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-        utils.authenticationRepository.setUnlocked(false)
+        utils.deviceEntryRepository.setUnlocked(false)
         sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
         sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
index 9260f63..617e310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
@@ -60,7 +60,7 @@
 
         underTest.startMonitoring()
         // There are two receivers registered
-        verify(context, times(2))
+        verify(context, times(1))
             .registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler))
         verify(packageManager).registerPackageMonitorCallback(any(), eq(USER.getIdentifier()))
         // context will be used to get PackageManager, the test should clear invocations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 5414259..677108c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -18,10 +18,13 @@
 
 import android.app.ActivityOptions
 import android.app.PendingIntent
+import android.content.Context
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -30,11 +33,13 @@
 import com.android.wm.shell.taskview.TaskView
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -43,31 +48,31 @@
 @TestableLooper.RunWithLooper
 class DetailDialogTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var taskView: TaskView
-    @Mock
-    private lateinit var broadcastSender: BroadcastSender
-    @Mock
-    private lateinit var controlViewHolder: ControlViewHolder
-    @Mock
-    private lateinit var pendingIntent: PendingIntent
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
+    @Rule
+    @JvmField
+    val activityRule: ActivityScenarioRule<EmptyTestActivity> =
+        ActivityScenarioRule(EmptyTestActivity::class.java)
+
+    @Mock private lateinit var taskView: TaskView
+    @Mock private lateinit var broadcastSender: BroadcastSender
+    @Mock private lateinit var controlViewHolder: ControlViewHolder
+    @Mock private lateinit var pendingIntent: PendingIntent
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: DetailDialog
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        underTest = createDialog(pendingIntent)
     }
 
     @Test
     fun testPendingIntentIsUnModified() {
-        // GIVEN the dialog is created with a PendingIntent
-        val dialog = createDialog(pendingIntent)
-
         // WHEN the TaskView is initialized
-        dialog.stateCallback.onInitialized()
+        underTest.stateCallback.onInitialized()
 
         // THEN the PendingIntent used to call startActivity is unmodified by systemui
         verify(taskView).startActivity(eq(pendingIntent), any(), any(), any())
@@ -75,11 +80,8 @@
 
     @Test
     fun testActivityOptionsAllowBal() {
-        // GIVEN the dialog is created with a PendingIntent
-        val dialog = createDialog(pendingIntent)
-
         // WHEN the TaskView is initialized
-        dialog.stateCallback.onInitialized()
+        underTest.stateCallback.onInitialized()
 
         val optionsCaptor = argumentCaptor<ActivityOptions>()
 
@@ -90,17 +92,41 @@
             .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
         assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission)
             .isTrue()
+        assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
     }
 
-    private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
+    @Test
+    fun testDismissRemovesTheTask() {
+        activityRule.scenario.onActivity {
+            underTest = createDialog(pendingIntent, it)
+            underTest.show()
+
+            underTest.dismiss()
+
+            verify(taskView).removeTask()
+            verify(taskView, never()).release()
+        }
+    }
+
+    @Test
+    fun testTaskRemovalReleasesTaskView() {
+        underTest.stateCallback.onTaskRemovalStarted(0)
+
+        verify(taskView).release()
+    }
+
+    private fun createDialog(
+        pendingIntent: PendingIntent,
+        context: Context = mContext,
+    ): DetailDialog {
         return DetailDialog(
-            mContext,
+            context,
             broadcastSender,
             taskView,
             pendingIntent,
             controlViewHolder,
             keyguardStateController,
-            activityStarter
+            activityStarter,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
new file mode 100644
index 0000000..8e8cbe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -0,0 +1,127 @@
+package com.android.systemui.deviceentry.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    private val testUtils = SceneTestUtils(this)
+    private val testScope = testUtils.testScope
+    private val userRepository = FakeUserRepository()
+
+    private lateinit var underTest: DeviceEntryRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        userRepository.setUserInfos(USER_INFOS)
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+        underTest =
+            DeviceEntryRepositoryImpl(
+                applicationScope = testScope.backgroundScope,
+                backgroundDispatcher = testUtils.testDispatcher,
+                userRepository = userRepository,
+                lockPatternUtils = lockPatternUtils,
+                keyguardBypassController = keyguardBypassController,
+                keyguardStateController = keyguardStateController,
+            )
+    }
+
+    @Test
+    fun isUnlocked() =
+        testScope.runTest {
+            whenever(keyguardStateController.isUnlocked).thenReturn(false)
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+
+            runCurrent()
+            assertThat(isUnlocked).isFalse()
+
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            Mockito.verify(keyguardStateController, Mockito.atLeastOnce())
+                .addCallback(captor.capture())
+
+            whenever(keyguardStateController.isUnlocked).thenReturn(true)
+            captor.value.onUnlockedChanged()
+            runCurrent()
+            assertThat(isUnlocked).isTrue()
+
+            whenever(keyguardStateController.isUnlocked).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            runCurrent()
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun isInsecureLockscreenEnabled() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false)
+            whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true)
+
+            userRepository.setSelectedUserInfo(USER_INFOS[0])
+            assertThat(underTest.isInsecureLockscreenEnabled()).isTrue()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(underTest.isInsecureLockscreenEnabled()).isFalse()
+        }
+
+    @Test
+    fun isBypassEnabled_disabledInController() =
+        testScope.runTest {
+            whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
+            whenever(keyguardBypassController.bypassEnabled).thenAnswer { false }
+            assertThat(underTest.isBypassEnabled()).isFalse()
+        }
+
+    @Test
+    fun isBypassEnabled_enabledInController() =
+        testScope.runTest {
+            whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true }
+            whenever(keyguardBypassController.bypassEnabled).thenAnswer { true }
+            assertThat(underTest.isBypassEnabled()).isTrue()
+        }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(
+                    /* id= */ 100,
+                    /* name= */ "First user",
+                    /* flags= */ 0,
+                ),
+                UserInfo(
+                    /* id= */ 101,
+                    /* name= */ "Second user",
+                    /* flags= */ 0,
+                ),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
new file mode 100644
index 0000000..55582e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -0,0 +1,234 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryInteractorTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
+    private val sceneInteractor = utils.sceneInteractor()
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val underTest =
+        utils.deviceEntryInteractor(
+            repository = repository,
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
+
+    @Test
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.apply {
+                setInsecureLockscreenEnabled(false)
+
+                // Toggle isUnlocked, twice.
+                //
+                // This is done because the underTest.isUnlocked flow doesn't receive values from
+                // just changing the state above; the actual isUnlocked state needs to change to
+                // cause the logic under test to "pick up" the current state again.
+                //
+                // It is done twice to make sure that we don't actually change the isUnlocked state
+                // from what it originally was.
+                setUnlocked(!isUnlocked.value)
+                runCurrent()
+                setUnlocked(!isUnlocked.value)
+                runCurrent()
+            }
+
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun isDeviceEntered_onLockscreenWithSwipe_isFalse() =
+        testScope.runTest {
+            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+
+            assertThat(isDeviceEntered).isFalse()
+        }
+
+    @Test
+    fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() =
+        testScope.runTest {
+            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+            runCurrent()
+            switchToScene(SceneKey.Shade)
+
+            assertThat(isDeviceEntered).isFalse()
+        }
+
+    @Test
+    fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+            runCurrent()
+            switchToScene(SceneKey.Gone)
+
+            assertThat(isDeviceEntered).isTrue()
+        }
+
+    @Test
+    fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+            runCurrent()
+            switchToScene(SceneKey.Gone)
+            runCurrent()
+            switchToScene(SceneKey.Shade)
+
+            assertThat(isDeviceEntered).isTrue()
+        }
+
+    @Test
+    fun isDeviceEntered_onBouncer_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
+            )
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+            runCurrent()
+            switchToScene(SceneKey.Bouncer)
+
+            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            assertThat(isDeviceEntered).isFalse()
+        }
+
+    @Test
+    fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+            assertThat(canSwipeToEnter).isTrue()
+        }
+
+    @Test
+    fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+            assertThat(canSwipeToEnter).isFalse()
+        }
+
+    @Test
+    fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            switchToScene(SceneKey.Lockscreen)
+            runCurrent()
+            switchToScene(SceneKey.Gone)
+
+            val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+            assertThat(canSwipeToEnter).isFalse()
+        }
+
+    @Test
+    fun isAuthenticationRequired_lockedAndSecured_true() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setUnlocked(false)
+            runCurrent()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+
+            assertThat(underTest.isAuthenticationRequired()).isTrue()
+        }
+
+    @Test
+    fun isAuthenticationRequired_lockedAndNotSecured_false() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setUnlocked(false)
+            runCurrent()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+            assertThat(underTest.isAuthenticationRequired()).isFalse()
+        }
+
+    @Test
+    fun isAuthenticationRequired_unlockedAndSecured_false() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setUnlocked(true)
+            runCurrent()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+
+            assertThat(underTest.isAuthenticationRequired()).isFalse()
+        }
+
+    @Test
+    fun isAuthenticationRequired_unlockedAndNotSecured_false() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setUnlocked(true)
+            runCurrent()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+            assertThat(underTest.isAuthenticationRequired()).isFalse()
+        }
+
+    @Test
+    fun isBypassEnabled_enabledInRepository_true() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setBypassEnabled(true)
+            assertThat(underTest.isBypassEnabled()).isTrue()
+        }
+
+    @Test
+    fun isBypassEnabled_disabledInRepository_false() =
+        testScope.runTest {
+            utils.deviceEntryRepository.setBypassEnabled(false)
+            assertThat(underTest.isBypassEnabled()).isFalse()
+        }
+
+    private fun switchToScene(sceneKey: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index add601c..8d12e49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -158,8 +158,8 @@
         var progress = 0.5f
         sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
 
-        // GIVEN a progress event due to a touch on the slider track at threshold
-        progress += config.jumpThreshold
+        // GIVEN a progress event due to a touch on the slider track beyond threshold
+        progress += (config.jumpThreshold + 0.01f)
         sliderEventProducer.sendEvent(
             SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, progress)
         )
@@ -191,7 +191,6 @@
         // THEN the tracker moves to the jump-track location selected state
         assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED)
         assertThat(mSeekableSliderTracker.isWaiting).isFalse()
-        verify(sliderStateListener).onUpperBookend()
         verifyNoMoreInteractions(sliderStateListener)
     }
 
@@ -214,7 +213,6 @@
         // THEN the tracker moves to the JUMP_TRACK_LOCATION_SELECTED state
         assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED)
         assertThat(mSeekableSliderTracker.isWaiting).isFalse()
-        verify(sliderStateListener).onLowerBookend()
         verifyNoMoreInteractions(sliderStateListener)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 9a2936e..7a13a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -102,7 +102,7 @@
             notificationShadeWindowController, powerManager, wallpaperManager
         )
         keyguardUnlockAnimationController.setLauncherUnlockController(
-            launcherUnlockAnimationController)
+            "", launcherUnlockAnimationController)
 
         whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
         whenever(powerManager.isInteractive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9d96ac7..291dda20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -104,6 +104,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeWindowLogger;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -185,6 +186,7 @@
     private @Mock SysuiColorExtractor mColorExtractor;
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+    private @Mock ShadeInteractor mShadeInteractor;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
@@ -249,6 +251,7 @@
                 mScreenOffAnimationController,
                 mAuthController,
                 mShadeExpansionStateManager,
+                () -> mShadeInteractor,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 2691860..f93051c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -81,7 +80,6 @@
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
     @Mock private lateinit var dozeParameters: DozeParameters
@@ -103,7 +101,6 @@
                 screenLifecycle,
                 biometricUnlockController,
                 keyguardStateController,
-                keyguardBypassController,
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 dozeParameters,
@@ -213,23 +210,9 @@
         }
 
     @Test
-    fun isBypassEnabled_disabledInController() {
-        whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
-        whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
-        assertThat(underTest.isBypassEnabled()).isFalse()
-    }
-
-    @Test
-    fun isBypassEnabled_enabledInController() {
-        whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
-        whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
-        assertThat(underTest.isBypassEnabled()).isTrue()
-    }
-
-    @Test
     fun isAodAvailable() = runTest {
         val flow = underTest.isAodAvailable
-        var isAodAvailable = collectLastValue(flow)
+        val isAodAvailable = collectLastValue(flow)
         runCurrent()
 
         val callback =
@@ -273,29 +256,6 @@
         }
 
     @Test
-    fun isKeyguardUnlocked() =
-        testScope.runTest {
-            whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
-
-            runCurrent()
-            assertThat(isKeyguardUnlocked).isFalse()
-
-            val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
-
-            whenever(keyguardStateController.isUnlocked).thenReturn(true)
-            captor.value.onUnlockedChanged()
-            runCurrent()
-            assertThat(isKeyguardUnlocked).isTrue()
-
-            whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            captor.value.onKeyguardShowingChanged()
-            runCurrent()
-            assertThat(isKeyguardUnlocked).isFalse()
-        }
-
-    @Test
     fun isDozing() =
         testScope.runTest {
             underTest.setIsDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 90e217f..82c7fa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -15,8 +15,6 @@
  *
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
@@ -27,13 +25,11 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -42,62 +38,53 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardInteractorTest : SysuiTestCase() {
-    private lateinit var commandQueue: FakeCommandQueue
-    private lateinit var featureFlags: FakeFeatureFlags
-    private lateinit var testScope: TestScope
 
-    private lateinit var underTest: KeyguardInteractor
-    private lateinit var repository: FakeKeyguardRepository
-    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
-    private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var shadeRepository: FakeShadeRepository
-    private lateinit var sceneInteractor: SceneInteractor
-    private lateinit var transitionState: MutableStateFlow<ObservableTransitionState>
+    private val testUtils = SceneTestUtils(this)
+    private val testScope = testUtils.testScope
+    private val repository = testUtils.keyguardRepository
+    private val sceneInteractor = testUtils.sceneInteractor()
+    private val commandQueue = FakeCommandQueue()
+    private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) }
+    private val bouncerRepository = FakeKeyguardBouncerRepository()
+    private val configurationRepository = FakeConfigurationRepository()
+    private val shadeRepository = FakeShadeRepository()
+    private val transitionState: MutableStateFlow<ObservableTransitionState> =
+        MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
+
+    private val underTest =
+        KeyguardInteractor(
+            repository = repository,
+            commandQueue = commandQueue,
+            featureFlags = featureFlags,
+            sceneContainerFlags = testUtils.sceneContainerFlags,
+            deviceEntryRepository = testUtils.deviceEntryRepository,
+            bouncerRepository = bouncerRepository,
+            configurationRepository = configurationRepository,
+            shadeRepository = shadeRepository,
+            sceneInteractorProvider = { sceneInteractor },
+        )
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        commandQueue = FakeCommandQueue()
-        val sceneTestUtils = SceneTestUtils(this)
-        testScope = sceneTestUtils.testScope
-        repository = sceneTestUtils.keyguardRepository
-        bouncerRepository = FakeKeyguardBouncerRepository()
-        configurationRepository = FakeConfigurationRepository()
-        shadeRepository = FakeShadeRepository()
-        sceneInteractor = sceneTestUtils.sceneInteractor()
-        transitionState = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
         sceneInteractor.setTransitionState(transitionState)
-        underTest =
-            KeyguardInteractor(
-                repository = repository,
-                commandQueue = commandQueue,
-                featureFlags = featureFlags,
-                sceneContainerFlags = sceneTestUtils.sceneContainerFlags,
-                bouncerRepository = bouncerRepository,
-                configurationRepository = configurationRepository,
-                shadeRepository = shadeRepository,
-                sceneInteractorProvider = { sceneInteractor },
-            )
     }
 
     @Test
     fun onCameraLaunchDetected() =
         testScope.runTest {
             val flow = underTest.onCameraLaunchDetected
-            var cameraLaunchSource = collectLastValue(flow)
+            val cameraLaunchSource = collectLastValue(flow)
             runCurrent()
 
             commandQueue.doForEachCallback {
@@ -175,7 +162,7 @@
 
     @Test
     fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
-        var isVisible = collectLastValue(underTest.isKeyguardVisible)
+        val isVisible = collectLastValue(underTest.isKeyguardVisible)
         repository.setKeyguardShowing(true)
         repository.setKeyguardOccluded(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index f2636c5..591653e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1226,7 +1226,6 @@
 
             // GIVEN the keyguard is showing locked
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            keyguardRepository.setKeyguardUnlocked(false)
             runCurrent()
             shadeRepository.setShadeModel(
                 ShadeModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 1681cfd..6e94691 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -37,10 +37,6 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
 
     private val underTest = createLockscreenSceneViewModel()
 
@@ -49,8 +45,8 @@
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+            utils.deviceEntryRepository.setUnlocked(true)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -61,7 +57,7 @@
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -88,7 +84,11 @@
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            authenticationInteractor = authenticationInteractor,
+            deviceEntryInteractor =
+                utils.deviceEntryInteractor(
+                    authenticationInteractor = utils.authenticationInteractor(),
+                    sceneInteractor = utils.sceneInteractor(),
+                ),
             communalInteractor = utils.communalInteractor(),
             longPress =
                 KeyguardLongPressViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 25faeef..de57b60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -81,6 +81,7 @@
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
     @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
+    @Mock lateinit var logger: MediaViewLogger
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -121,7 +122,8 @@
                 notifPanelEvents,
                 settings,
                 fakeHandler,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                logger,
             )
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 126dd63..43cf1b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -16,12 +16,15 @@
 
 package com.android.systemui.qs.external
 
+import android.app.IUriGrantsManager
 import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ServiceInfo
+import android.graphics.Bitmap
+import android.graphics.Canvas
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.os.Handler
@@ -49,6 +52,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -67,6 +71,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import java.util.Arrays
+
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -74,10 +80,8 @@
 class CustomTileTest : SysuiTestCase() {
 
     companion object {
-        const val packageName = "test_package"
         const val className = "test_class"
-        val componentName = ComponentName(packageName, className)
-        val TILE_SPEC = CustomTile.toSpec(componentName)
+        val UID = 12345
     }
 
     @Mock private lateinit var tileHost: QSHost
@@ -94,11 +98,36 @@
     @Mock private lateinit var serviceInfo: ServiceInfo
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
     @Mock private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var ugm: IUriGrantsManager
 
     private var displayTracker = FakeDisplayTracker(mContext)
     private lateinit var customTile: CustomTile
     private lateinit var testableLooper: TestableLooper
-    private lateinit var customTileBuilder: CustomTile.Builder
+    private val packageName = context.packageName
+    private val componentName = ComponentName(packageName, className)
+    private val TILE_SPEC = CustomTile.toSpec(componentName)
+
+    private val customTileFactory = object : CustomTile.Factory {
+        override fun create(action: String, userContext: Context): CustomTile {
+            return CustomTile(
+                { tileHost },
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                FalsingManagerFake(),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                action,
+                userContext,
+                customTileStatePersister,
+                tileServices,
+                displayTracker,
+                ugm,
+            )
+        }
+    }
 
     @Before
     fun setUp() {
@@ -116,24 +145,13 @@
 
         `when`(packageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
                 .thenReturn(serviceInfo)
+        `when`(packageManager.getResourcesForApplication(any<ApplicationInfo>()))
+                .thenReturn(context.resources)
+
         serviceInfo.applicationInfo = applicationInfo
 
-        customTileBuilder = CustomTile.Builder(
-                { tileHost },
-                uiEventLogger,
-                testableLooper.looper,
-                Handler(testableLooper.looper),
-                FalsingManagerFake(),
-                metricsLogger,
-                statusBarStateController,
-                activityStarter,
-                qsLogger,
-                customTileStatePersister,
-                tileServices,
-                displayTracker
-        )
 
-        customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         customTile.initialize()
         testableLooper.processAllMessages()
     }
@@ -146,7 +164,7 @@
         `when`(userContext.packageManager).thenReturn(packageManager)
         `when`(userContext.userId).thenReturn(10)
 
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, userContext)
         tile.initialize()
         testableLooper.processAllMessages()
 
@@ -156,7 +174,7 @@
     @Test
     fun testToggleableTileHasBooleanState() {
         `when`(tileServiceManager.isToggleableTile).thenReturn(true)
-        customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         customTile.initialize()
         testableLooper.processAllMessages()
 
@@ -173,7 +191,7 @@
     @Test
     fun testValueUpdatedInBooleanTile() {
         `when`(tileServiceManager.isToggleableTile).thenReturn(true)
-        customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         customTile.initialize()
         testableLooper.processAllMessages()
 
@@ -219,7 +237,7 @@
         val t = Tile().apply {
             state = Tile.STATE_INACTIVE
         }
-        customTile.updateTileState(t)
+        customTile.updateTileState(t, UID)
         testableLooper.processAllMessages()
 
         verify(customTileStatePersister, never()).persistState(any(), any())
@@ -243,7 +261,7 @@
         `when`(tileServiceManager.isActiveTile).thenReturn(true)
         `when`(customTileStatePersister
                 .readState(TileServiceKey(componentName, customTile.user))).thenReturn(t)
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.initialize()
         testableLooper.processAllMessages()
 
@@ -281,11 +299,11 @@
         }
         `when`(tileServiceManager.isActiveTile).thenReturn(true)
 
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.initialize()
         testableLooper.processAllMessages()
 
-        tile.updateTileState(t)
+        tile.updateTileState(t, UID)
 
         testableLooper.processAllMessages()
 
@@ -297,13 +315,13 @@
     fun testAvailableBeforeInitialization() {
         `when`(packageManager.getApplicationInfo(anyString(), anyInt()))
                 .thenThrow(PackageManager.NameNotFoundException())
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         assertTrue(tile.isAvailable)
     }
 
     @Test
     fun testNotAvailableAfterInitializationWithoutIcon() {
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         reset(tileHost)
         tile.initialize()
         testableLooper.processAllMessages()
@@ -315,7 +333,7 @@
     fun testInvalidPendingIntentDoesNotStartActivity() {
         val pi = mock(PendingIntent::class.java)
         `when`(pi.isActivity).thenReturn(false)
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
 
         assertThrows(IllegalArgumentException::class.java) {
             tile.qsTile.activityLaunchForClick = pi
@@ -333,7 +351,7 @@
     fun testValidPendingIntentWithNoClickDoesNotStartActivity() {
         val pi = mock(PendingIntent::class.java)
         `when`(pi.isActivity).thenReturn(true)
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.qsTile.activityLaunchForClick = pi
 
         testableLooper.processAllMessages()
@@ -347,7 +365,7 @@
     fun testValidPendingIntentStartsActivity() {
         val pi = mock(PendingIntent::class.java)
         `when`(pi.isActivity).thenReturn(true)
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.qsTile.activityLaunchForClick = pi
 
         tile.handleClick(mock(LaunchableFrameLayout::class.java))
@@ -363,7 +381,7 @@
     fun testActiveTileListensOnceAfterCreated() {
         `when`(tileServiceManager.isActiveTile).thenReturn(true)
 
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.initialize()
         tile.postStale()
         testableLooper.processAllMessages()
@@ -376,7 +394,7 @@
     fun testActiveTileDoesntListenAfterFirstTime() {
         `when`(tileServiceManager.isActiveTile).thenReturn(true)
 
-        val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+        val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
         tile.initialize()
         // Make sure we have an icon in the tile because we don't have a default icon
         // This should not be overridden by the retrieved tile that has null icon.
@@ -424,19 +442,128 @@
         // Set the tile to listening and apply the tile (unmodified)
         customTile.handleSetListening(true)
         testableLooper.processAllMessages()
-        customTile.updateTileState(tile)
+        customTile.updateTileState(tile, UID)
         customTile.refreshState()
         testableLooper.processAllMessages()
 
         assertThat(customTile.state.label).isEqualTo(label2)
     }
 
-    private fun copyTileUsingParcel(t: Tile): Tile {
-        val parcel = Parcel.obtain()
-        parcel.setDataPosition(0)
-        t.writeToParcel(parcel, 0)
-        parcel.setDataPosition(0)
+    @Test
+    fun uriIconLoadSuccess_correctIcon() {
+        val size = 100
+        val icon = mock(Icon::class.java)
+        val drawable = context.getDrawable(R.drawable.cloud)!!
+        whenever(icon.loadDrawable(any())).thenReturn(drawable)
+        whenever(icon.loadDrawableCheckingUriGrant(
+            any(),
+            eq(ugm),
+            anyInt(),
+            anyString())
+        ).thenReturn(drawable)
 
-        return Tile.CREATOR.createFromParcel(parcel)
+        serviceInfo.icon = R.drawable.android
+
+        customTile.handleSetListening(true)
+        testableLooper.processAllMessages()
+        customTile.handleSetListening(false)
+        testableLooper.processAllMessages()
+
+        val tile = copyTileUsingParcel(customTile.qsTile)
+        tile.icon = icon
+
+        customTile.updateTileState(tile, UID)
+
+        customTile.refreshState()
+        testableLooper.processAllMessages()
+
+        verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+        assertThat(
+                areDrawablesEqual(
+                        customTile.state.iconSupplier.get().getDrawable(context),
+                        drawable,
+                        size
+                )
+        ).isTrue()
     }
+
+    @Test
+    fun uriIconLoadFailsWithoutGrant_defaultIcon() {
+        val size = 100
+        val drawable = context.getDrawable(R.drawable.cloud)!!
+        val icon = mock(Icon::class.java)
+        whenever(icon.loadDrawable(any())).thenReturn(drawable)
+        whenever(icon.loadDrawableCheckingUriGrant(
+            any(),
+            eq(ugm),
+            anyInt(),
+            anyString())
+        ).thenReturn(null)
+
+        // Give it an icon to prevent issues
+        serviceInfo.icon = R.drawable.android
+
+        customTile.handleSetListening(true)
+        testableLooper.processAllMessages()
+        customTile.handleSetListening(false)
+        testableLooper.processAllMessages()
+
+        val tile = copyTileUsingParcel(customTile.qsTile)
+        tile.icon = icon
+
+        customTile.updateTileState(tile, UID)
+
+        customTile.refreshState()
+        testableLooper.processAllMessages()
+
+        verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+        assertThat(
+                areDrawablesEqual(
+                        customTile.state.iconSupplier.get().getDrawable(context),
+                        context.getDrawable(R.drawable.android)!!,
+                        size
+                )
+        ).isTrue()
+    }
+}
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+    val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+    val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+    val canvas1 = Canvas(bm1)
+    val canvas2 = Canvas(bm2)
+
+    drawable1.setBounds(0, 0, size, size)
+    drawable2.setBounds(0, 0, size, size)
+
+    drawable1.draw(canvas1)
+    drawable2.draw(canvas2)
+
+    return equalBitmaps(bm1, bm2).also {
+        bm1.recycle()
+        bm2.recycle()
+    }
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+    if (a.width != b.width || a.height != b.height) return false
+    val w = a.width
+    val h = a.height
+    val aPix = IntArray(w * h)
+    val bPix = IntArray(w * h)
+    a.getPixels(aPix, 0, w, 0, 0, w, h)
+    b.getPixels(bPix, 0, w, 0, 0, w, h)
+    return Arrays.equals(aPix, bPix)
+}
+
+private fun copyTileUsingParcel(t: Tile): Tile {
+    val parcel = Parcel.obtain()
+    parcel.setDataPosition(0)
+    t.writeToParcel(parcel, 0)
+    parcel.setDataPosition(0)
+
+    return Tile.CREATOR.createFromParcel(parcel)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 365e8a5..78c2acf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -16,6 +16,11 @@
 
 package com.android.systemui.qs.external
 
+import android.app.IUriGrantsManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -26,11 +31,21 @@
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Arrays
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -40,12 +55,19 @@
     companion object {
         private const val APP_NAME = "App name"
         private const val LABEL = "Label"
+        private const val PACKAGE = "package"
+        private const val UID = 12345
+        private val DEFAULT_ICON = R.drawable.android
     }
 
     private lateinit var dialog: TileRequestDialog
 
+    @Mock
+    private lateinit var ugm: IUriGrantsManager
+
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         // Create in looper so we can make sure that the tile is fully updated
         TestableLooper.get(this).runWithLooper {
             dialog = TileRequestDialog(mContext)
@@ -62,9 +84,9 @@
     @Test
     fun setTileData_hasCorrectViews() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -77,9 +99,9 @@
     @Test
     fun setTileData_hasCorrectAppName() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -90,9 +112,9 @@
     @Test
     fun setTileData_hasCorrectLabel() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -105,9 +127,9 @@
     @Test
     fun setTileData_hasIcon() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -119,9 +141,9 @@
 
     @Test
     fun setTileData_nullIcon_hasIcon() {
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, null)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -134,9 +156,9 @@
     @Test
     fun setTileData_hasNoStateDescription() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -150,9 +172,9 @@
     @Test
     fun setTileData_tileNotClickable() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -167,9 +189,9 @@
     @Test
     fun setTileData_tileHasCorrectContentDescription() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
-        dialog.setTileData(tileData)
+        dialog.setTileData(tileData, ugm)
         dialog.show()
 
         TestableLooper.get(this).processAllMessages()
@@ -179,4 +201,111 @@
 
         assertThat(tile.contentDescription).isEqualTo(LABEL)
     }
+
+    @Test
+    fun uriIconLoadSuccess_correctIcon() {
+        val tintColor = Color.BLACK
+        val icon = Mockito.mock(Icon::class.java)
+        val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+            setTint(tintColor)
+        }
+        whenever(icon.loadDrawable(any())).thenReturn(drawable)
+        whenever(icon.loadDrawableCheckingUriGrant(
+            any(),
+            eq(ugm),
+            anyInt(),
+            anyString())
+        ).thenReturn(drawable)
+
+        val size = 100
+
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+        dialog.setTileData(tileData, ugm)
+        dialog.show()
+
+        TestableLooper.get(this).processAllMessages()
+
+        verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+        val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+        val tile = content.getChildAt(1) as QSTileView
+
+        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+            setTint(tintColor)
+        }
+
+        assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
+    }
+
+    @Test
+    fun uriIconLoadFail_defaultIcon() {
+        val tintColor = Color.BLACK
+        val icon = Mockito.mock(Icon::class.java)
+        val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+            setTint(tintColor)
+        }
+        whenever(icon.loadDrawable(any())).thenReturn(drawable)
+        whenever(icon.loadDrawableCheckingUriGrant(
+            any(),
+            eq(ugm),
+            anyInt(),
+            anyString())
+        ).thenReturn(null)
+
+        val size = 100
+
+        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+        dialog.setTileData(tileData, ugm)
+        dialog.show()
+
+        TestableLooper.get(this).processAllMessages()
+
+        verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+        val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+        val tile = content.getChildAt(1) as QSTileView
+
+        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+            setTint(tintColor)
+        }
+
+        val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
+            setTint(tintColor)
+        }
+
+        assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
+    }
 }
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+    val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+    val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+    val canvas1 = Canvas(bm1)
+    val canvas2 = Canvas(bm2)
+
+    drawable1.setBounds(0, 0, size, size)
+    drawable2.setBounds(0, 0, size, size)
+
+    drawable1.draw(canvas1)
+    drawable2.draw(canvas2)
+
+    return equalBitmaps(bm1, bm2).also {
+        bm1.recycle()
+        bm2.recycle()
+    }
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+    if (a.width != b.width || a.height != b.height) return false
+    val w = a.width
+    val h = a.height
+    val aPix = IntArray(w * h)
+    val bPix = IntArray(w * h)
+    a.getPixels(aPix, 0, w, 0, 0, w, h)
+    b.getPixels(bPix, 0, w, 0, 0, w, h)
+    return Arrays.equals(aPix, bPix)
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index ccfb5cf..3afa6ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.external
 
+import android.app.IUriGrantsManager
 import android.app.StatusBarManager
 import android.content.ComponentName
 import android.content.DialogInterface
@@ -57,6 +58,7 @@
         private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
         private const val TEST_APP_NAME = "App"
         private const val TEST_LABEL = "Label"
+        private const val TEST_UID = 12345
     }
 
     @Mock
@@ -71,6 +73,8 @@
     private lateinit var logger: TileRequestDialogEventLogger
     @Mock
     private lateinit var icon: Icon
+    @Mock
+    private lateinit var ugm: IUriGrantsManager
 
     private val instanceIdSequence = InstanceIdSequenceFake(1_000)
     private lateinit var controller: TileServiceRequestController
@@ -88,7 +92,8 @@
                 qsHost,
                 commandQueue,
                 commandRegistry,
-                logger
+                logger,
+                ugm,
         ) {
             tileRequestDialog
         }
@@ -98,10 +103,24 @@
 
     @Test
     fun requestTileAdd_dataIsPassedToDialog() {
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                Callback(),
+        )
 
         verify(tileRequestDialog).setTileData(
-                TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+                TileRequestDialog.TileData(
+                        TEST_UID,
+                        TEST_APP_NAME,
+                        TEST_LABEL,
+                        icon,
+                        TEST_COMPONENT.packageName,
+                ),
+                ugm,
         )
     }
 
@@ -110,7 +129,14 @@
         `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
 
         assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
         verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
@@ -120,7 +146,7 @@
     fun tileAlreadyAdded_logged() {
         `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
 
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
 
         verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
         verify(logger, never()).logDialogShown(anyString(), any())
@@ -129,19 +155,33 @@
 
     @Test
     fun showAllUsers_set() {
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                Callback(),
+        )
         verify(tileRequestDialog).setShowForAllUsers(true)
     }
 
     @Test
     fun cancelOnTouchOutside_set() {
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                Callback(),
+        )
         verify(tileRequestDialog).setCanceledOnTouchOutside(true)
     }
 
     @Test
     fun dialogShown_logged() {
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
 
         verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
     }
@@ -152,7 +192,14 @@
                 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
 
         cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -165,7 +212,7 @@
         val cancelListenerCaptor =
                 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
 
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
         val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
 
         verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -185,7 +232,14 @@
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
 
         clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
@@ -199,7 +253,7 @@
         val clickListenerCaptor =
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
 
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
         val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
 
         verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
@@ -219,7 +273,14 @@
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
 
         clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
@@ -233,7 +294,7 @@
         val clickListenerCaptor =
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
 
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
         val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
 
         verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
@@ -257,10 +318,24 @@
         val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
 
-        captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+        captor.value.requestAddTile(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                Callback(),
+        )
 
         verify(tileRequestDialog).setTileData(
-                TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+                TileRequestDialog.TileData(
+                        TEST_UID,
+                        TEST_APP_NAME,
+                        TEST_LABEL,
+                        icon,
+                        TEST_COMPONENT.packageName,
+                ),
+                ugm,
         )
     }
 
@@ -271,7 +346,7 @@
         verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
         val c = Callback()
 
-        captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
+        captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
 
         assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
     }
@@ -288,7 +363,14 @@
                 throw RemoteException()
             }
         }
-        captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        captor.value.requestAddTile(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
 
         cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -300,7 +382,14 @@
             ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
 
         dismissListenerCaptor.value.onDismiss(tileRequestDialog)
@@ -317,7 +406,14 @@
             ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
         verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
 
@@ -338,7 +434,14 @@
             ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
 
         val callback = Callback()
-        controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+        controller.requestTileAdd(
+                TEST_UID,
+                TEST_COMPONENT,
+                TEST_APP_NAME,
+                TEST_LABEL,
+                icon,
+                callback,
+        )
         verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
         verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index e222542..067218a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -51,16 +51,17 @@
 import com.android.systemui.qs.tiles.UiModeNightTile
 import com.android.systemui.qs.tiles.WorkModeTile
 import com.android.systemui.util.leak.GarbageMonitor
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+import org.mockito.Mockito.`when` as whenever
 
 private val specMap = mapOf(
         "internet" to InternetTile::class.java,
@@ -98,7 +99,7 @@
 class QSFactoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var qsHost: QSHost
-    @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+    @Mock private lateinit var customTileFactory: CustomTile.Factory
     @Mock private lateinit var customTile: CustomTile
 
     @Mock private lateinit var internetTile: InternetTile
@@ -139,7 +140,7 @@
 
         whenever(qsHost.context).thenReturn(mContext)
         whenever(qsHost.userContext).thenReturn(mContext)
-        whenever(customTileBuilder.build()).thenReturn(customTile)
+        whenever(customTileFactory.create(anyString(), any())).thenReturn(customTile)
 
         val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>(
             "internet" to Provider { internetTile },
@@ -174,7 +175,7 @@
 
         factory = QSFactoryImpl(
                 { qsHost },
-                { customTileBuilder },
+                { customTileFactory },
                 tileMap,
         )
         // When adding/removing tiles, fix also [specMap] and [tileMap]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
deleted file mode 100644
index 077c813..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.android.systemui.qs.tiles.base
-
-import android.content.Intent
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
-
-    @Mock private lateinit var activityStarted: ActivityStarter
-
-    lateinit var underTest: QSTileIntentUserActionHandler
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        underTest = QSTileIntentUserActionHandler(activityStarted)
-    }
-
-    @Test
-    fun testPassesIntentToStarter() {
-        val intent = Intent("test.ACTION")
-
-        underTest.handle(null, intent)
-
-        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
new file mode 100644
index 0000000..06b7a9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.actions
+
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityStarted: ActivityStarter
+
+    lateinit var underTest: QSTileIntentUserActionHandler
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = QSTileIntentUserActionHandler(activityStarted)
+    }
+
+    @Test
+    fun testPassesIntentToStarter() {
+        val intent = Intent("test.ACTION")
+
+        underTest.handle(null, intent)
+
+        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
new file mode 100644
index 0000000..4f25d12
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import android.content.ComponentName
+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.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class DisabledByPolicyInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var restrictedLockProxy: RestrictedLockProxy
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var context: Context
+
+    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    lateinit var underTest: DisabledByPolicyInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            DisabledByPolicyInteractorImpl(
+                context,
+                activityStarter,
+                restrictedLockProxy,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun testEnabledWhenNoAdmin() =
+        testScope.runTest {
+            whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(null)
+
+            assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+                .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+        }
+
+    @Test
+    fun testDisabledWhenAdminWithNoRestrictions() =
+        testScope.runTest {
+            val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+            whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin)
+            whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+                .thenReturn(false)
+
+            val result =
+                underTest.isDisabled(TEST_USER, TEST_RESTRICTION)
+                    as DisabledByPolicyInteractor.PolicyResult.TileDisabled
+            assertThat(result.admin).isEqualTo(admin)
+        }
+
+    @Test
+    fun testEnabledWhenAdminWithRestrictions() =
+        testScope.runTest {
+            whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(ADMIN)
+            whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+                .thenReturn(true)
+
+            assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+                .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+        }
+
+    @Test
+    fun testHandleDisabledByPolicy() {
+        val result =
+            underTest.handlePolicyResult(
+                DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
+            )
+
+        val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+        assertThat(result).isTrue()
+        verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+        assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
+    }
+
+    @Test
+    fun testHandleEnabled() {
+        val result =
+            underTest.handlePolicyResult(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+
+        assertThat(result).isFalse()
+        verify(activityStarter, never())
+            .postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+    }
+
+    private companion object {
+        const val TEST_USER = 1
+        const val TEST_RESTRICTION = "test_restriction"
+
+        val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls")
+
+        val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index eacb080..9024c6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.graphics.drawable.ShapeDrawable
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.android.internal.logging.InstanceId
 import com.android.systemui.RoboPilotTest
@@ -10,6 +10,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
@@ -29,12 +30,13 @@
 // TODO(b/299909368): Add more tests
 @MediumTest
 @RoboPilotTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
 
     private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
     private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
+    private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
 
     private val testCoroutineDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testCoroutineDispatcher)
@@ -68,18 +70,18 @@
         scope: TestScope,
         config: QSTileConfig = TEST_QS_TILE_CONFIG,
     ): QSTileViewModel =
-        object :
-            BaseQSTileViewModel<Any>(
-                config,
-                fakeQSTileUserActionInteractor,
-                fakeQSTileDataInteractor,
-                object : QSTileDataToStateMapper<Any> {
-                    override fun map(config: QSTileConfig, data: Any): QSTileState =
-                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
-                },
-                testCoroutineDispatcher,
-                tileScope = scope.backgroundScope,
-            ) {}
+        BaseQSTileViewModel(
+            config,
+            fakeQSTileUserActionInteractor,
+            fakeQSTileDataInteractor,
+            object : QSTileDataToStateMapper<Any> {
+                override fun map(config: QSTileConfig, data: Any): QSTileState =
+                    QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+            },
+            fakeDisabledByPolicyInteractor,
+            testCoroutineDispatcher,
+            scope.backgroundScope,
+        )
 
     private companion object {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 8ae8930..f1c99d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -48,11 +48,6 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
-
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
 
     private var mobileIconsViewModel: MobileIconsViewModel =
@@ -85,10 +80,17 @@
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
 
+        val authenticationInteractor = utils.authenticationInteractor()
+
         underTest =
             QuickSettingsSceneViewModel(
                 bouncerInteractor =
                     utils.bouncerInteractor(
+                        deviceEntryInteractor =
+                            utils.deviceEntryInteractor(
+                                authenticationInteractor = authenticationInteractor,
+                                sceneInteractor = sceneInteractor,
+                            ),
                         authenticationInteractor = authenticationInteractor,
                         sceneInteractor = sceneInteractor,
                     ),
@@ -101,7 +103,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -114,7 +116,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 85bd92b..5259013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -83,7 +83,6 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-
     private val sceneContainerConfig = utils.fakeSceneContainerConfig()
     private val sceneRepository =
         utils.fakeSceneContainerRepository(
@@ -93,14 +92,12 @@
         utils.sceneInteractor(
             repository = sceneRepository,
         )
-
-    private val authenticationRepository = utils.authenticationRepository()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = authenticationRepository,
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
-
     private val communalInteractor = utils.communalInteractor()
 
     private val transitionState =
@@ -116,6 +113,7 @@
 
     private val bouncerInteractor =
         utils.bouncerInteractor(
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
@@ -128,7 +126,7 @@
     private val lockscreenSceneViewModel =
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            authenticationInteractor = authenticationInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
             communalInteractor = communalInteractor,
             longPress =
                 KeyguardLongPressViewModel(
@@ -178,12 +176,12 @@
         shadeSceneViewModel =
             ShadeSceneViewModel(
                 applicationScope = testScope.backgroundScope,
-                authenticationInteractor = authenticationInteractor,
+                deviceEntryInteractor = deviceEntryInteractor,
                 bouncerInteractor = bouncerInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
             )
 
-        authenticationRepository.setUnlocked(false)
+        utils.deviceEntryRepository.setUnlocked(false)
 
         val displayTracker = FakeDisplayTracker(context)
         val sysUiState = SysUiState(displayTracker)
@@ -191,6 +189,7 @@
             SceneContainerStartable(
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = sceneInteractor,
+                deviceEntryInteractor = deviceEntryInteractor,
                 authenticationInteractor = authenticationInteractor,
                 keyguardInteractor = keyguardInteractor,
                 flags = utils.sceneContainerFlags,
@@ -417,13 +416,13 @@
         // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
         // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
         // is not an observable that can trigger a new evaluation.
-        authenticationRepository.setLockscreenEnabled(
+        utils.deviceEntryRepository.setInsecureLockscreenEnabled(
             authMethod !is DomainLayerAuthenticationMethodModel.None
         )
-        authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+        utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
         if (!authMethod.isSecure) {
             // When the auth method is not secure, the device is never considered locked.
-            authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
         }
         runCurrent()
     }
@@ -528,14 +527,14 @@
             .that(authMethod.isSecure)
             .isTrue()
 
-        authenticationRepository.setUnlocked(false)
+        utils.deviceEntryRepository.setUnlocked(false)
         runCurrent()
     }
 
     /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
     private fun TestScope.unlockDevice() {
         assertWithMessage("Cannot unlock a device that's already unlocked!")
-            .that(authenticationInteractor.isUnlocked.value)
+            .that(deviceEntryInteractor.isUnlocked.value)
             .isFalse()
 
         emulateUserDrivenTransition(SceneKey.Bouncer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16fdf8e..00a20cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -59,17 +59,13 @@
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val sceneContainerFlags = utils.sceneContainerFlags
-    private val authenticationRepository = utils.authenticationRepository()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = authenticationRepository,
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
-    private val keyguardRepository = utils.keyguardRepository
-    private val keyguardInteractor =
-        utils.keyguardInteractor(
-            repository = keyguardRepository,
-        )
+    private val keyguardInteractor = utils.keyguardInteractor()
     private val sysUiState: SysUiState = mock()
     private val falsingCollector: FalsingCollector = mock()
 
@@ -77,6 +73,7 @@
         SceneContainerStartable(
             applicationScope = testScope.backgroundScope,
             sceneInteractor = sceneInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             keyguardInteractor = keyguardInteractor,
             flags = sceneContainerFlags,
@@ -141,7 +138,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
             underTest.start()
 
-            authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -157,7 +154,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
             underTest.start()
 
-            authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -173,7 +170,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -189,7 +186,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -205,7 +202,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -251,7 +248,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -267,7 +264,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -283,7 +280,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -300,9 +297,9 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -389,11 +386,11 @@
             runCurrent()
             verify(falsingCollector).setShowingAod(false)
 
-            keyguardRepository.setIsDozing(true)
+            utils.keyguardRepository.setIsDozing(true)
             runCurrent()
             verify(falsingCollector).setShowingAod(true)
 
-            keyguardRepository.setIsDozing(false)
+            utils.keyguardRepository.setIsDozing(false)
             runCurrent()
             verify(falsingCollector, times(2)).setShowingAod(false)
         }
@@ -401,7 +398,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
         testScope.runTest {
-            keyguardRepository.setAodAvailable(false)
+            utils.keyguardRepository.setAodAvailable(false)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -414,31 +411,31 @@
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(ASLEEP)
+            utils.keyguardRepository.setWakefulnessModel(ASLEEP)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, times(1)).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
             verify(falsingCollector, times(1)).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(ASLEEP)
+            utils.keyguardRepository.setWakefulnessModel(ASLEEP)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
             verify(falsingCollector, times(2)).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(2)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
@@ -448,7 +445,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
         testScope.runTest {
-            keyguardRepository.setAodAvailable(true)
+            utils.keyguardRepository.setAodAvailable(true)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -461,31 +458,31 @@
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(ASLEEP)
+            utils.keyguardRepository.setWakefulnessModel(ASLEEP)
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(ASLEEP)
+            utils.keyguardRepository.setWakefulnessModel(ASLEEP)
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
             verify(falsingCollector, never()).onScreenOff()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
@@ -521,8 +518,8 @@
     ): MutableStateFlow<ObservableTransitionState> {
         assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
         sceneContainerFlags.enabled = true
-        authenticationRepository.setUnlocked(isDeviceUnlocked)
-        keyguardRepository.setBypassEnabled(isBypassEnabled)
+        utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
+        utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
         val transitionStateFlow =
             MutableStateFlow<ObservableTransitionState>(
                 ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -534,8 +531,10 @@
             sceneInteractor.onSceneChanged(SceneModel(it), "reason")
         }
         authenticationMethod?.let {
-            authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
-            authenticationRepository.setLockscreenEnabled(
+            utils.authenticationRepository.setAuthenticationMethod(
+                authenticationMethod.toDataLayer()
+            )
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(
                 authenticationMethod != AuthenticationMethodModel.None
             )
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 2d00e8c..2ed2090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -46,22 +47,34 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
 
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -77,8 +90,10 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.test.TestScope;
+
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
 
@@ -102,6 +117,9 @@
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
+    private SceneTestUtils mUtils = new SceneTestUtils(this);
+    private TestScope mTestScope = mUtils.getTestScope();
+    private ShadeInteractor mShadeInteractor;
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private float mPreferredRefreshRate = -1;
@@ -119,6 +137,23 @@
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
+        mShadeInteractor =
+                new ShadeInteractor(
+                        mTestScope.getBackgroundScope(),
+                        new FakeDisableFlagsRepository(),
+                        new FakeSceneContainerFlags(),
+                        mUtils::sceneInteractor,
+                        new FakeKeyguardRepository(),
+                        new FakeUserSetupRepository(),
+                        mock(DeviceProvisionedController.class),
+                        mock(UserInteractor.class),
+                        new SharedNotificationContainerInteractor(
+                                new FakeConfigurationRepository(),
+                                mContext,
+                                new ResourcesSplitShadeStateController()),
+                        new FakeShadeRepository()
+                );
+
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
@@ -136,6 +171,7 @@
                 mScreenOffAnimationController,
                 mAuthController,
                 mShadeExpansionStateManager,
+                () -> mShadeInteractor,
                 mShadeWindowLogger) {
                     @Override
                     protected boolean isDebuggable() {
@@ -272,9 +308,9 @@
 
     @Test
     public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
-        mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
+        mNotificationShadeWindowController.onShadeOrQsExpanded(true);
         clearInvocations(mWindowManager);
-        mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
+        mNotificationShadeWindowController.onShadeOrQsExpanded(true);
         verifyNoMoreInteractions(mWindowManager);
         mNotificationShadeWindowController.setNotificationShadeFocusable(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5c75d9c..602bd5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -47,9 +47,10 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
+    private val authenticationInteractor = utils.authenticationInteractor()
+    private val deviceEntryInteractor =
+        utils.deviceEntryInteractor(
+            authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
 
@@ -88,9 +89,10 @@
         underTest =
             ShadeSceneViewModel(
                 applicationScope = testScope.backgroundScope,
-                authenticationInteractor = authenticationInteractor,
+                deviceEntryInteractor = deviceEntryInteractor,
                 bouncerInteractor =
                     utils.bouncerInteractor(
+                        deviceEntryInteractor = deviceEntryInteractor,
                         authenticationInteractor = authenticationInteractor,
                         sceneInteractor = sceneInteractor,
                     ),
@@ -103,7 +105,7 @@
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -113,7 +115,7 @@
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -122,7 +124,7 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -134,7 +136,7 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -147,7 +149,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(true)
+            utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -160,7 +162,7 @@
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.deviceEntryRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 39accfb..19863ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,9 +16,18 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,6 +41,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -49,8 +59,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
@@ -66,6 +74,7 @@
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -112,7 +121,6 @@
     private NotificationEntry mCurrentUserNotif;
     private NotificationEntry mSecondaryUserNotif;
     private NotificationEntry mWorkProfileNotif;
-    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     @Before
     public void setUp() {
@@ -127,8 +135,11 @@
         mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
                 UserManager.USER_TYPE_PROFILE_MANAGED);
 
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
         when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
-                mCurrentUser, mSecondaryUser, mWorkUser));
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 Handler.createAsync(Looper.myLooper()));
 
@@ -153,14 +164,14 @@
 
     @Test
     public void testLockScreenShowNotificationsFalse() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenShowNotificationsTrue() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
         mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
@@ -227,6 +238,7 @@
 
         // THEN work profile notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
     }
 
     @Test
@@ -238,6 +250,7 @@
 
         // THEN work profile notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
     }
 
     @Test
@@ -295,21 +308,12 @@
 
     @Test
     public void testUserSwitchedCallsOnUserSwitching() {
-        mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, true);
         mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
                 mContext);
         verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
     }
 
     @Test
-    public void testUserSwitchedCallsOnUserSwitched() {
-        mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, false);
-        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id,
-                mContext);
-        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
-    }
-
-    @Test
     public void testIsLockscreenPublicMode() {
         assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
         mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
@@ -348,6 +352,262 @@
         verify(listener, never()).onNotificationStateChanged();
     }
 
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Ignore("b/286230167")
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_userAll() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(USER_ALL));
+    }
+
+    @Test
+    @Ignore("b/286230167")
+    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+
+        // TODO (b/286230167): enable assertion
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+
+        // TODO (b/286230167): enable assertion. It's currently called 4 times.
+        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications_userAll() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build()));
+    }
+
+    @Test
+    public void testDevicePolicyPrivateNotifications_secondary() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+
+        // TODO (b/286230167): enable assertion. It's currently called 5 times.
+        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    public void testHideNotifications_primary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Ignore("b/286230167")
+    @Test
+    public void testHideNotifications_workProfile() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+
+        TestNotificationLockscreenUserManager lockscreenUserManager
+                = new TestNotificationLockscreenUserManager(mContext);
+        lockscreenUserManager.setUpWithPresenter(mPresenter);
+
+        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testShowNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+
+        TestNotificationLockscreenUserManager lockscreenUserManager
+                = new TestNotificationLockscreenUserManager(mContext);
+        lockscreenUserManager.setUpWithPresenter(mPresenter);
+
+        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Ignore("b/286230167")
+    @Test
+    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_settingsChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // User disables
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Ignore("b/286230167")
+    @Test
+    public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // DevicePolicy disables notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
+        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
     private class TestNotificationLockscreenUserManager
             extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
@@ -368,8 +628,7 @@
                     mKeyguardStateController,
                     mSettings,
                     mock(DumpManager.class),
-                    mock(LockPatternUtils.class),
-                    mFakeFeatureFlags);
+                    mock(LockPatternUtils.class));
         }
 
         public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 2c8900c..61ba464 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -16,11 +16,18 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import com.android.systemui.statusbar.data.model.StatusBarAppearance
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeStatusBarModeRepository : StatusBarModeRepository {
     override val isTransientShown = MutableStateFlow(false)
     override val isInFullscreenMode = MutableStateFlow(false)
+    override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
+    override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
+
+    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
 
     override fun showTransient() {
         isTransientShown.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 27c1ba3..d1a46fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -16,20 +16,36 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import android.graphics.Rect
 import android.view.WindowInsets
 import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
 import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.BoundsPair
+import com.android.systemui.statusbar.phone.LetterboxAppearance
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -37,14 +53,26 @@
 class StatusBarModeRepositoryImplTest : SysuiTestCase() {
     private val testScope = TestScope()
     private val commandQueue = mock<CommandQueue>()
+    private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
+    private val statusBarBoundsProvider = mock<StatusBarBoundsProvider>()
+    private val statusBarFragmentComponent =
+        mock<StatusBarFragmentComponent>().also {
+            whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
+        }
+    private val ongoingCallRepository = OngoingCallRepository()
 
     private val underTest =
         StatusBarModeRepositoryImpl(
                 testScope.backgroundScope,
                 DISPLAY_ID,
                 commandQueue,
+                letterboxAppearanceCalculator,
+                ongoingCallRepository,
             )
-            .apply { this.start() }
+            .apply {
+                this.start()
+                this.onStatusBarViewInitialized(statusBarFragmentComponent)
+            }
 
     private val commandQueueCallback: CommandQueue.Callbacks
         get() {
@@ -53,6 +81,15 @@
             return callbackCaptor.value
         }
 
+    private val statusBarBoundsChangeListener: StatusBarBoundsProvider.BoundsChangeListener
+        get() {
+            val callbackCaptor = argumentCaptor<StatusBarBoundsProvider.BoundsChangeListener>()
+            verify(statusBarBoundsProvider).addChangeListener(capture(callbackCaptor))
+            return callbackCaptor.value
+        }
+
+    @Before fun setUp() {}
+
     @Test
     fun isTransientShown_commandQueueShow_wrongDisplayId_notUpdated() {
         commandQueueCallback.showTransient(
@@ -228,6 +265,240 @@
             assertThat(latest).isTrue()
         }
 
+    @Test
+    fun statusBarAppearance_navBarColorManaged_matchesCallbackValue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(navbarColorManagedByIme = true)
+
+            assertThat(latest!!.navbarColorManagedByIme).isTrue()
+
+            onSystemBarAttributesChanged(navbarColorManagedByIme = false)
+
+            assertThat(latest!!.navbarColorManagedByIme).isFalse()
+        }
+
+    @Test
+    fun statusBarAppearance_appearanceRegions_noLetterboxDetails_usesCallbackValues() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            whenever(
+                    letterboxAppearanceCalculator.getLetterboxAppearance(
+                        eq(APPEARANCE),
+                        eq(APPEARANCE_REGIONS),
+                        eq(LETTERBOX_DETAILS),
+                        any(),
+                    )
+                )
+                .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
+
+            // WHEN the letterbox details are empty
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE,
+                appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+                letterboxDetails = emptyArray(),
+            )
+
+            // THEN the appearance regions passed to the callback are used, *not*
+            // REGIONS_FROM_LETTERBOX_CALCULATOR
+            assertThat(latest!!.appearanceRegions).isEqualTo(APPEARANCE_REGIONS)
+            assertThat(latest!!.appearanceRegions).isNotEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
+        }
+
+    @Test
+    fun statusBarAppearance_appearanceRegions_letterboxDetails_usesLetterboxCalculator() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            whenever(
+                    letterboxAppearanceCalculator.getLetterboxAppearance(
+                        eq(APPEARANCE),
+                        eq(APPEARANCE_REGIONS),
+                        eq(LETTERBOX_DETAILS),
+                        any(),
+                    )
+                )
+                .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
+
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE,
+                appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+                letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
+            )
+
+            assertThat(latest!!.appearanceRegions).isEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
+        }
+
+    @Test
+    fun statusBarAppearance_boundsChanged_appearanceReFetched() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            // First, start with some appearances
+            val startingLetterboxAppearance =
+                LetterboxAppearance(
+                    APPEARANCE_LIGHT_STATUS_BARS,
+                    listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1)))
+                )
+            whenever(
+                    letterboxAppearanceCalculator.getLetterboxAppearance(
+                        eq(APPEARANCE),
+                        eq(APPEARANCE_REGIONS),
+                        eq(LETTERBOX_DETAILS),
+                        any(),
+                    )
+                )
+                .thenReturn(startingLetterboxAppearance)
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE,
+                appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
+                letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
+            )
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
+            assertThat(latest!!.appearanceRegions)
+                .isEqualTo(startingLetterboxAppearance.appearanceRegions)
+
+            // WHEN there's a new appearance and we get new status bar bounds
+            val newLetterboxAppearance =
+                LetterboxAppearance(
+                    APPEARANCE_LOW_PROFILE_BARS,
+                    listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40)))
+                )
+            whenever(
+                    letterboxAppearanceCalculator.getLetterboxAppearance(
+                        eq(APPEARANCE),
+                        eq(APPEARANCE_REGIONS),
+                        eq(LETTERBOX_DETAILS),
+                        any(),
+                    )
+                )
+                .thenReturn(newLetterboxAppearance)
+            statusBarBoundsChangeListener.onStatusBarBoundsChanged(
+                BoundsPair(Rect(0, 0, 50, 50), Rect(0, 0, 60, 60))
+            )
+
+            // THEN the new appearances are used
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
+            assertThat(latest!!.appearanceRegions)
+                .isEqualTo(newLetterboxAppearance.appearanceRegions)
+        }
+
+    @Test
+    fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            ongoingCallRepository.setHasOngoingCall(true)
+            onSystemBarAttributesChanged(
+                requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+        }
+
+    @Test
+    fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            ongoingCallRepository.setHasOngoingCall(true)
+            onSystemBarAttributesChanged(
+                requestedVisibleTypes = WindowInsets.Type.statusBars(),
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+        }
+
+    @Test
+    fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            ongoingCallRepository.setHasOngoingCall(false)
+            onSystemBarAttributesChanged(
+                requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+        }
+
+    @Test
+    fun statusBarMode_transientShown_semiTransparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            underTest.showTransient()
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+        }
+
+    @Test
+    fun statusBarMode_appearanceLowProfileAndOpaque_lightsOut() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT)
+        }
+
+    @Test
+    fun statusBarMode_appearanceLowProfile_lightsOutTransparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE_LOW_PROFILE_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
+        }
+
+    @Test
+    fun statusBarMode_appearanceOpaque_opaque() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+        }
+
+    @Test
+    fun statusBarMode_appearanceSemiTransparent_semiTransparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(
+                appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+        }
+
+    @Test
+    fun statusBarMode_appearanceNone_transparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            onSystemBarAttributesChanged(
+                appearance = 0,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
+        }
+
     private fun onSystemBarAttributesChanged(
         displayId: Int = DISPLAY_ID,
         @WindowInsetsController.Appearance appearance: Int = APPEARANCE_OPAQUE_STATUS_BARS,
@@ -253,5 +524,21 @@
 
     private companion object {
         const val DISPLAY_ID = 5
+        private const val APPEARANCE = APPEARANCE_LIGHT_STATUS_BARS
+        private val APPEARANCE_REGION = AppearanceRegion(APPEARANCE, Rect(0, 0, 150, 300))
+        private val APPEARANCE_REGIONS = listOf(APPEARANCE_REGION)
+        private val LETTERBOX_DETAILS =
+            listOf(
+                LetterboxDetails(
+                    /* letterboxInnerBounds= */ Rect(0, 0, 10, 10),
+                    /* letterboxFullBounds= */ Rect(0, 0, 20, 20),
+                    /* appAppearance= */ 0
+                )
+            )
+        private val REGIONS_FROM_LETTERBOX_CALCULATOR =
+            listOf(AppearanceRegion(APPEARANCE, Rect(0, 0, 10, 20)))
+        private const val LETTERBOXED_APPEARANCE = APPEARANCE_LOW_PROFILE_BARS
+        private val CALCULATOR_LETTERBOX_APPEARANCE =
+            LetterboxAppearance(LETTERBOXED_APPEARANCE, REGIONS_FROM_LETTERBOX_CALCULATOR)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index c71c0c57..a5d3484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -35,14 +34,11 @@
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.view.HapticFeedbackConstants;
-import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.internal.statusbar.LetterboxDetails;
-import com.android.internal.view.AppearanceRegion;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
@@ -100,7 +96,6 @@
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private Vibrator mVibrator;
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
-    @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
     @Mock private UserTracker mUserTracker;
     @Mock private QSHost mQSHost;
@@ -139,7 +134,6 @@
                 Optional.of(mVibrator),
                 new DisableFlagsLogger(),
                 DEFAULT_DISPLAY,
-                mSystemBarAttributesListener,
                 mCameraLauncherLazy,
                 mUserTracker,
                 mQSHost,
@@ -197,62 +191,6 @@
     }
 
     @Test
-    public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() {
-        int displayId = DEFAULT_DISPLAY;
-        int appearance = 123;
-        AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
-        boolean navbarColorManagedByIme = true;
-        int behavior = 456;
-        int requestedVisibleTypes = WindowInsets.Type.systemBars();
-        String packageName = "test package name";
-        LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
-
-        mSbcqCallbacks.onSystemBarAttributesChanged(
-                displayId,
-                appearance,
-                appearanceRegions,
-                navbarColorManagedByIme,
-                behavior,
-                requestedVisibleTypes,
-                packageName,
-                letterboxDetails);
-
-        verify(mSystemBarAttributesListener).onSystemBarAttributesChanged(
-                displayId,
-                appearance,
-                appearanceRegions,
-                navbarColorManagedByIme,
-                behavior,
-                requestedVisibleTypes,
-                packageName,
-                letterboxDetails
-        );
-    }
-
-    @Test
-    public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() {
-        int appearance = 123;
-        AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
-        boolean navbarColorManagedByIme = true;
-        int behavior = 456;
-        int requestedVisibleTypes = WindowInsets.Type.systemBars();
-        String packageName = "test package name";
-        LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
-
-        mSbcqCallbacks.onSystemBarAttributesChanged(
-                DEFAULT_DISPLAY + 1,
-                appearance,
-                appearanceRegions,
-                navbarColorManagedByIme,
-                behavior,
-                requestedVisibleTypes,
-                packageName,
-                letterboxDetails);
-
-        verifyZeroInteractions(mSystemBarAttributesListener);
-    }
-
-    @Test
     public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
         mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 0b171b2..e33fa22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -165,7 +165,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -295,7 +294,6 @@
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
     @Mock private WallpaperController mWallpaperController;
-    @Mock private OngoingCallController mOngoingCallController;
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -535,7 +533,6 @@
                 mBrightnessSliderFactory,
                 mScreenOffAnimationController,
                 mWallpaperController,
-                mOngoingCallController,
                 mStatusBarHideIconsForBouncerManager,
                 mLockscreenTransitionController,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index b2dc0dc..e7b287c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -26,7 +26,6 @@
 import com.android.internal.view.AppearanceRegion
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -47,15 +46,12 @@
         private val TEST_APPEARANCE_REGION_BOUNDS = Rect(0, 0, 20, 100)
         private val TEST_APPEARANCE_REGION =
             AppearanceRegion(TEST_APPEARANCE, TEST_APPEARANCE_REGION_BOUNDS)
-        private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
+        private val TEST_APPEARANCE_REGIONS = listOf(TEST_APPEARANCE_REGION)
         private val TEST_WINDOW_BOUNDS = Rect(0, 0, 500, 500)
     }
 
     @get:Rule var expect = Expect.create()
 
-    @Mock private lateinit var lightBarController: LightBarController
-    @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
-    @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
 
@@ -64,24 +60,21 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
         calculator =
-            LetterboxAppearanceCalculator(
-                lightBarController, dumpManager, letterboxBackgroundProvider)
-        calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+            LetterboxAppearanceCalculator(context, dumpManager, letterboxBackgroundProvider)
         whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
         whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
     }
 
     @Test
     fun getLetterboxAppearance_overlapStartSide_returnsOriginalWithScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(50, 50, 150, 150))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         expect
             .that(letterboxAppearance.appearance)
@@ -91,13 +84,13 @@
 
     @Test
     fun getLetterboxAppearance_overlapEndSide_returnsOriginalWithScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(150, 50, 250, 150))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         expect
             .that(letterboxAppearance.appearance)
@@ -114,14 +107,12 @@
         val statusBarStartSideBoundsCopy = Rect(statusBarStartSideBounds)
         val statusBarEndSideBoundsCopy = Rect(statusBarEndSideBounds)
         val letterBoxInnerBoundsCopy = Rect(letterBoxInnerBounds)
-        whenever(statusBarBoundsProvider.visibleStartSideBounds)
-                .thenReturn(statusBarStartSideBounds)
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(statusBarEndSideBounds)
 
         calculator.getLetterboxAppearance(
                 TEST_APPEARANCE,
                 TEST_APPEARANCE_REGIONS,
-                arrayOf(letterboxWithInnerBounds(letterBoxInnerBounds))
+            listOf(letterboxWithInnerBounds(letterBoxInnerBounds)),
+            BoundsPair(statusBarStartSideBounds, statusBarEndSideBounds)
         )
 
         expect.that(statusBarStartSideBounds).isEqualTo(statusBarStartSideBoundsCopy)
@@ -132,13 +123,13 @@
     @Test
     fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
         whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         expect
                 .that(letterboxAppearance.appearance)
@@ -148,66 +139,66 @@
 
     @Test
     fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
     }
 
     @Test
     fun getLetterboxAppearance_letterboxContainsStartSide_returnsAppearanceWithoutScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(0, 0, 101, 101))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
     }
 
     @Test
     fun getLetterboxAppearance_letterboxContainsEndSide_returnsAppearanceWithoutScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(199, 0, 301, 101))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
     }
 
     @Test
     fun getLetterboxAppearance_letterboxContainsEntireStatusBar_returnsAppearanceWithoutScrim() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val start = Rect(0, 0, 100, 100)
+        val end = Rect(200, 0, 300, 100)
         val letterbox = letterboxWithInnerBounds(Rect(0, 0, 300, 100))
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end))
 
         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
     }
 
     @Test
     fun getLetterboxAppearance_returnsAdaptedAppearanceRegions_basedOnLetterboxInnerBounds() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+        val start = Rect(0, 0, 0, 0)
+        val end = Rect(0, 0, 0, 0)
         val letterbox = letterboxWithInnerBounds(Rect(150, 0, 300, 800))
         val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+                TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end))
 
         val letterboxAdaptedRegion = letterboxRegion.copy(bounds = letterbox.letterboxInnerBounds)
         assertThat(letterboxAppearance.appearanceRegions.toList()).contains(letterboxAdaptedRegion)
@@ -216,8 +207,8 @@
 
     @Test
     fun getLetterboxAppearance_returnsDefaultAppearanceRegions_basedOnLetterboxOuterBounds() {
-        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
-        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+        val start = Rect(0, 0, 0, 0)
+        val end = Rect(0, 0, 0, 0)
         val letterbox =
             letterboxWithBounds(
                 innerBounds = Rect(left = 25, top = 0, right = 75, bottom = 100),
@@ -226,16 +217,20 @@
 
         val letterboxAppearance =
             calculator.getLetterboxAppearance(
-                TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+                TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end))
 
         val outerRegions =
             listOf(
                 AppearanceRegion(
-                    DEFAULT_APPEARANCE, Rect(left = 0, top = 0, right = 25, bottom = 100)),
+                    DEFAULT_APPEARANCE,
+                    Rect(left = 0, top = 0, right = 25, bottom = 100),
+                ),
                 AppearanceRegion(
-                    DEFAULT_APPEARANCE, Rect(left = 75, top = 0, right = 100, bottom = 100)),
+                    DEFAULT_APPEARANCE,
+                    Rect(left = 75, top = 0, right = 100, bottom = 100),
+                ),
             )
-        assertThat(letterboxAppearance.appearanceRegions.toList())
+        assertThat(letterboxAppearance.appearanceRegions)
             .containsAtLeastElementsIn(outerRegions)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index df3c1e5..c45ecf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,12 +22,14 @@
 
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -42,11 +44,16 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.view.AppearanceRegion;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.model.StatusBarMode;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,6 +61,10 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import kotlinx.coroutines.test.TestScope;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -62,10 +73,16 @@
 
     private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
     private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
+    private static final BoundsPair STATUS_BAR_BOUNDS = new BoundsPair(
+            /* start= */ new Rect(0, 0, 10, 10),
+            /* end= */ new Rect(0, 0, 20, 20));
     private LightBarTransitionsController mLightBarTransitionsController;
     private LightBarTransitionsController mNavBarController;
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final FakeStatusBarModeRepository mStatusBarModeRepository =
+            new FakeStatusBarModeRepository();
 
     @Before
     public void setup() {
@@ -77,11 +94,14 @@
                 mLightBarTransitionsController);
         mLightBarController = new LightBarController(
                 mContext,
+                new JavaAdapter(mTestScope),
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
+                mStatusBarModeRepository,
                 mock(DumpManager.class),
                 new FakeDisplayTracker(mContext));
+        mLightBarController.start();
     }
 
     private static GradientColors makeColors(@ColorInt int bgColor) {
@@ -96,13 +116,19 @@
     public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksLight() {
         final Rect firstBounds = new Rect(0, 0, 1, 1);
         final Rect secondBounds = new Rect(1, 0, 2, 1);
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = Arrays.asList(
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         verify(mStatusBarIconController).setIconsDarkArea(eq(null));
         verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
     }
@@ -111,13 +137,19 @@
     public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightOneStackDark() {
         final Rect firstBounds = new Rect(0, 0, 1, 1);
         final Rect secondBounds = new Rect(1, 0, 2, 1);
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = Arrays.asList(
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
                 new AppearanceRegion(0 /* appearance */, secondBounds)
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
         verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
         assertTrue(captor.getValue().contains(firstBounds));
@@ -128,13 +160,19 @@
     public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackDarkOneStackLight() {
         final Rect firstBounds = new Rect(0, 0, 1, 1);
         final Rect secondBounds = new Rect(1, 0, 2, 1);
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = Arrays.asList(
                 new AppearanceRegion(0 /* appearance */, firstBounds),
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
         verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
         assertTrue(captor.getValue().contains(secondBounds));
@@ -146,14 +184,20 @@
         final Rect firstBounds = new Rect(0, 0, 1, 1);
         final Rect secondBounds = new Rect(1, 0, 2, 1);
         final Rect thirdBounds = new Rect(2, 0, 3, 1);
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = Arrays.asList(
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds),
                 new AppearanceRegion(0 /* appearance */, secondBounds),
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class);
         verify(mStatusBarIconController).setIconsDarkArea(captor.capture());
         assertTrue(captor.getValue().contains(firstBounds));
@@ -165,40 +209,121 @@
     public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksDark() {
         final Rect firstBounds = new Rect(0, 0, 1, 1);
         final Rect secondBounds = new Rect(1, 0, 2, 1);
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = Arrays.asList(
                 new AppearanceRegion(0 /* appearance */, firstBounds),
                 new AppearanceRegion(0 /* appearance */, secondBounds)
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean());
     }
 
     @Test
     public void testOnStatusBarAppearanceChanged_singleStack_light() {
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = List.of(
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         verify(mStatusBarIconController).setIconsDarkArea(eq(null));
         verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean());
     }
 
     @Test
     public void testOnStatusBarAppearanceChanged_singleStack_dark() {
-        final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{
+        final List<AppearanceRegion> appearanceRegions = List.of(
                 new AppearanceRegion(0, new Rect(0, 0, 1, 1))
-        };
-        mLightBarController.onStatusBarAppearanceChanged(
-                appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT,
-                false /* navbarColorManagedByIme */);
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
         verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean());
     }
 
     @Test
+    public void testOnStatusBarAppearanceChanged_statusBarModeChanged_statusRedrawn() {
+        final List<AppearanceRegion> appearanceRegions = List.of(
+                new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
+        );
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+        reset(mStatusBarIconController);
+
+        // WHEN the same appearance regions but different status bar mode is sent
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.LIGHTS_OUT_TRANSPARENT,
+                        STATUS_BAR_BOUNDS,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
+        // THEN the StatusBarIconController gets a redraw request
+        verify(mStatusBarIconController).setIconsDarkArea(any());
+    }
+
+    /** Regression test for b/301605450. */
+    @Test
+    public void testOnStatusBarAppearanceChanged_statusBarBoundsChanged_statusRedrawn() {
+        final List<AppearanceRegion> appearanceRegions = List.of(
+                new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
+        );
+        BoundsPair startingBounds = new BoundsPair(
+                /* start= */ new Rect(0, 0, 10, 10),
+                /* end= */ new Rect(0, 0, 20, 20));
+
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        startingBounds,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+        reset(mStatusBarIconController);
+
+        // WHEN the same appearance regions but different status bar bounds are sent
+        BoundsPair newBounds = new BoundsPair(
+                /* start= */ new Rect(0, 0, 30, 30),
+                /* end= */ new Rect(0, 0, 40, 40));
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
+                new StatusBarAppearance(
+                        StatusBarMode.TRANSPARENT,
+                        newBounds,
+                        appearanceRegions,
+                        /* navbarColorManagedByIme= */ false));
+        mTestScope.getTestScheduler().advanceUntilIdle();
+
+        // THEN the StatusBarIconController gets a redraw request
+        verify(mStatusBarIconController).setIconsDarkArea(any());
+    }
+
+    @Test
     public void validateNavBarChangesUpdateIcons() {
         // On the launcher in dark mode buttons are light
         mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
index d84010d..61da701 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -24,14 +24,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.any
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -60,8 +61,9 @@
         startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) }
         endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) }
 
-        boundsProvider =
-            StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent)
+        boundsProvider = StatusBarBoundsProvider(startSideContent, endSideContent)
+        boundsProvider.addChangeListener(boundsChangeListener)
+        reset(boundsChangeListener)
     }
 
     @Test
@@ -81,7 +83,7 @@
 
         startSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener).onStatusBarBoundsChanged()
+        verify(boundsChangeListener).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -90,7 +92,7 @@
 
         startSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -101,7 +103,7 @@
 
         startSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -111,7 +113,7 @@
 
         startSideContent.layout(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -121,7 +123,7 @@
 
         endSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener).onStatusBarBoundsChanged()
+        verify(boundsChangeListener).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -130,7 +132,7 @@
 
         endSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -141,7 +143,7 @@
 
         endSideContent.setBoundsOnScreen(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 
     @Test
@@ -151,7 +153,7 @@
 
         endSideContent.layout(newBounds)
 
-        verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+        verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any())
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
deleted file mode 100644
index 0cd2214..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-package com.android.systemui.statusbar.phone
-
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Display
-import android.view.WindowInsets
-import android.view.WindowInsetsController
-import android.view.WindowInsetsController.*
-import androidx.test.filters.SmallTest
-import com.android.internal.statusbar.LetterboxDetails
-import com.android.internal.view.AppearanceRegion
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class SystemBarAttributesListenerTest : SysuiTestCase() {
-
-    @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var lightBarController: LightBarController
-    @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
-
-    private lateinit var sysBarAttrsListener: SystemBarAttributesListener
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(
-                letterboxAppearanceCalculator.getLetterboxAppearance(
-                    anyInt(), anyObject(), anyObject()))
-            .thenReturn(TEST_LETTERBOX_APPEARANCE)
-
-        sysBarAttrsListener =
-            SystemBarAttributesListener(
-                centralSurfaces,
-                letterboxAppearanceCalculator,
-                lightBarController,
-                dumpManager)
-    }
-
-    @Test
-    fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() {
-        val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
-
-        changeSysBarAttrs(appearance)
-
-        verify(centralSurfaces).setAppearance(appearance)
-    }
-
-    @Test
-    fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
-
-        verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
-    }
-
-    @Test
-    fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
-        changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
-
-        verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
-    }
-
-    @Test
-    fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS)
-
-        verify(lightBarController)
-            .onStatusBarAppearanceChanged(
-                eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean())
-    }
-
-    @Test
-    fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
-
-        verify(lightBarController)
-            .onStatusBarAppearanceChanged(
-                eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
-                anyBoolean(),
-                anyInt(),
-                anyBoolean())
-    }
-
-    @Test
-    fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
-        reset(centralSurfaces, lightBarController)
-
-        sysBarAttrsListener.onStatusBarBoundsChanged()
-
-        verify(lightBarController)
-            .onStatusBarAppearanceChanged(
-                eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
-                anyBoolean(),
-                anyInt(),
-                anyBoolean())
-    }
-
-    @Test
-    fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
-        reset(centralSurfaces, lightBarController)
-
-        sysBarAttrsListener.onStatusBarBoundsChanged()
-
-        verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
-    }
-
-    @Test
-    fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
-        changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
-        reset(centralSurfaces, lightBarController)
-
-        sysBarAttrsListener.onStatusBarBoundsChanged()
-
-        verifyZeroInteractions(centralSurfaces, lightBarController)
-    }
-
-    private fun changeSysBarAttrs(@Appearance appearance: Int) {
-        changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
-    }
-
-    private fun changeSysBarAttrs(
-        @Appearance appearance: Int,
-        letterboxDetails: Array<LetterboxDetails>
-    ) {
-        changeSysBarAttrs(appearance, arrayOf(), letterboxDetails)
-    }
-
-    private fun changeSysBarAttrs(
-        @Appearance appearance: Int,
-        appearanceRegions: Array<AppearanceRegion>
-    ) {
-        changeSysBarAttrs(appearance, appearanceRegions, arrayOf())
-    }
-
-    private fun changeSysBarAttrs(
-        @Appearance appearance: Int,
-        appearanceRegions: Array<AppearanceRegion>,
-        letterboxDetails: Array<LetterboxDetails>
-    ) {
-        sysBarAttrsListener.onSystemBarAttributesChanged(
-            Display.DEFAULT_DISPLAY,
-            appearance,
-            appearanceRegions,
-            /* navbarColorManagedByIme= */ false,
-            WindowInsetsController.BEHAVIOR_DEFAULT,
-            WindowInsets.Type.defaultVisible(),
-            "package name",
-            letterboxDetails)
-    }
-
-    companion object {
-        private const val TEST_APPEARANCE =
-            APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
-        private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300))
-        private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
-        private val TEST_LETTERBOX_DETAILS =
-            arrayOf(
-                LetterboxDetails(
-                    /* letterboxInnerBounds= */ Rect(0, 0, 0, 0),
-                    /* letterboxFullBounds= */ Rect(0, 0, 0, 0),
-                    /* appAppearance= */ 0))
-        private val TEST_LETTERBOX_APPEARANCE =
-            LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf())
-    }
-}
-
-private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 32320b4..49de512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -85,6 +86,7 @@
     private val uiEventLoggerFake = UiEventLoggerFake()
     private val testScope = TestScope()
     private val statusBarModeRepository = FakeStatusBarModeRepository()
+    private val ongoingCallRepository = OngoingCallRepository()
 
     private lateinit var controller: OngoingCallController
     private lateinit var notifCollectionListener: NotifCollectionListener
@@ -111,6 +113,7 @@
         controller = OngoingCallController(
             testScope.backgroundScope,
             context,
+            ongoingCallRepository,
             notificationCollection,
             clock,
             mockActivityStarter,
@@ -140,10 +143,11 @@
     }
 
     @Test
-    fun onEntryUpdated_isOngoingCallNotif_listenerNotified() {
+    fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
     }
 
     @Test
@@ -154,10 +158,11 @@
     }
 
     @Test
-    fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
+    fun onEntryUpdated_notOngoingCallNotif_listenerAndRepoNotNotified() {
         notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
 
         verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
     }
 
     @Test
@@ -169,6 +174,16 @@
                 .onOngoingCallStateChanged(anyBoolean())
     }
 
+    @Test
+    fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() {
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+
+        notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
+
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+    }
+
     /** Regression test for b/191472854. */
     @Test
     fun onEntryUpdated_notifHasNullContentIntent_noCrash() {
@@ -276,6 +291,17 @@
     }
 
     @Test
+    fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() {
+        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+
+        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+    }
+
+    @Test
     fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() {
         val ongoingCallNotifEntry = createOngoingCallNotifEntry()
         notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
@@ -302,6 +328,22 @@
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
+    /** Regression test for b/188491504. */
+    @Test
+    fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_repoUpdated() {
+        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+
+        // Create another notification based on the ongoing call one, but remove the features that
+        // made it a call notification.
+        val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
+        removedEntryBuilder.modifyNotification(context).style = null
+
+        notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
+
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+    }
+
     @Test
     fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
         notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
@@ -313,6 +355,15 @@
     }
 
     @Test
+    fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_repoNotUpdated() {
+        notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
+
+        notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
+
+        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+    }
+
+    @Test
     fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() {
         assertThat(controller.hasOngoingCall()).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
new file mode 100644
index 0000000..56aa7d6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class OngoingCallRepositoryTest : SysuiTestCase() {
+    private val underTest = OngoingCallRepository()
+
+    @Test
+    fun hasOngoingCall_matchesSet() {
+        underTest.setHasOngoingCall(true)
+
+        assertThat(underTest.hasOngoingCall.value).isTrue()
+
+        underTest.setHasOngoingCall(false)
+
+        assertThat(underTest.hasOngoingCall.value).isFalse()
+    }
+}
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 15b9d61..c935dbb 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
@@ -17,13 +17,14 @@
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
@@ -181,6 +182,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -192,6 +195,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -203,6 +208,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -214,6 +221,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -225,6 +234,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -236,6 +247,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -247,6 +260,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -258,6 +273,8 @@
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index a520f6c..49a2648 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,8 +18,10 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -136,6 +138,10 @@
             // is used instead
             assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
             assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+            assertThat(
+                    (latest as WifiIcon.Visible).contentDescription.loadContentDescription(context)
+                )
+                .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 85359ca..bfc5bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -342,6 +342,31 @@
     }
 
     @Test
+    public void testShowToast_afterShowToast_animationListenerCleanup() throws RemoteException {
+        mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback, Display.DEFAULT_DISPLAY);
+        final SystemUIToast toast = mToastUI.mToast;
+
+        View view = verifyWmAddViewAndAttachToParent();
+        mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG,
+                null, Display.DEFAULT_DISPLAY);
+
+        if (toast.getOutAnimation() != null) {
+            assertThat(mToastUI.mToastOutAnimatorListener).isNotNull();
+            assertThat(toast.getOutAnimation().getListeners()
+                .contains(mToastUI.mToastOutAnimatorListener)).isTrue();
+            assertThat(toast.getOutAnimation().isRunning()).isTrue();
+            toast.getOutAnimation().cancel(); // end early if applicable
+            assertThat(toast.getOutAnimation().getListeners()).isNull();
+        }
+
+        verify(mWindowManager).removeViewImmediate(view);
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
+        verify(mCallback).onToastHidden();
+        assertThat(mToastUI.mToastOutAnimatorListener).isNull();
+    }
+
+    @Test
     public void testShowToast_logs() {
         mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
                 mCallback, Display.DEFAULT_DISPLAY);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 65b8b55..424218c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -87,16 +87,19 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -104,11 +107,14 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeWindowLogger;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationEntryHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -121,15 +127,19 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -176,6 +186,8 @@
 import java.util.List;
 import java.util.Optional;
 
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -299,6 +311,9 @@
     @Mock
     private Icon mAppBubbleIcon;
 
+    private SceneTestUtils mUtils = new SceneTestUtils(this);
+    private TestScope mTestScope = mUtils.getTestScope();
+    private ShadeInteractor mShadeInteractor;
     private ShellTaskOrganizer mShellTaskOrganizer;
     private TaskViewTransitions mTaskViewTransitions;
 
@@ -335,6 +350,22 @@
         when(mNotificationShadeWindowView.getViewTreeObserver())
                 .thenReturn(mock(ViewTreeObserver.class));
 
+        mShadeInteractor = new ShadeInteractor(
+                mTestScope.getBackgroundScope(),
+                new FakeDisableFlagsRepository(),
+                new FakeSceneContainerFlags(),
+                mUtils::sceneInteractor,
+                new FakeKeyguardRepository(),
+                new FakeUserSetupRepository(),
+                mock(DeviceProvisionedController.class),
+                mock(UserInteractor.class),
+                new SharedNotificationContainerInteractor(
+                        new FakeConfigurationRepository(),
+                        mContext,
+                        new ResourcesSplitShadeStateController()),
+                new FakeShadeRepository()
+        );
+
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
@@ -352,7 +383,9 @@
                 mScreenOffAnimationController,
                 mAuthController,
                 mShadeExpansionStateManager,
-                mShadeWindowLogger);
+                () -> mShadeInteractor,
+                mShadeWindowLogger
+        );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index f2e4528..4fc3e3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -24,20 +24,19 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 class FakeAuthenticationRepository(
+    private val deviceEntryRepository: FakeDeviceEntryRepository,
     private val currentTime: () -> Long,
 ) : AuthenticationRepository {
 
     private val _isAutoConfirmEnabled = MutableStateFlow(false)
     override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
 
-    private val _isUnlocked = MutableStateFlow(false)
-    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
     override val hintedPinLength: Int = HINTING_PIN_LENGTH
 
     private val _isPatternVisible = MutableStateFlow(true)
@@ -53,7 +52,6 @@
 
     override val minPatternLength: Int = 4
 
-    private var isLockscreenEnabled = true
     private var failedAttemptCount = 0
     private var throttlingEndTimestamp = 0L
     private var credentialOverride: List<Any>? = null
@@ -72,13 +70,9 @@
         credentialOverride = pin
     }
 
-    override suspend fun isLockscreenEnabled(): Boolean {
-        return isLockscreenEnabled
-    }
-
     override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
         failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
-        _isUnlocked.value = isSuccessful
+        deviceEntryRepository.setUnlocked(isSuccessful)
     }
 
     override suspend fun getPinLength(): Int {
@@ -97,18 +91,10 @@
         _throttling.value = throttlingModel
     }
 
-    fun setUnlocked(isUnlocked: Boolean) {
-        _isUnlocked.value = isUnlocked
-    }
-
     fun setAutoConfirmEnabled(isEnabled: Boolean) {
         _isAutoConfirmEnabled.value = isEnabled
     }
 
-    fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
-        this.isLockscreenEnabled = isLockscreenEnabled
-    }
-
     override suspend fun setThrottleDuration(durationMs: Int) {
         throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
new file mode 100644
index 0000000..5e60a09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -0,0 +1,35 @@
+package com.android.systemui.deviceentry.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [DeviceEntryRepository] */
+class FakeDeviceEntryRepository : DeviceEntryRepository {
+
+    private var isInsecureLockscreenEnabled = true
+    private var isBypassEnabled = false
+
+    private val _isUnlocked = MutableStateFlow(false)
+    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+    override fun isBypassEnabled(): Boolean {
+        return isBypassEnabled
+    }
+
+    override suspend fun isInsecureLockscreenEnabled(): Boolean {
+        return isInsecureLockscreenEnabled
+    }
+
+    fun setUnlocked(isUnlocked: Boolean) {
+        _isUnlocked.value = isUnlocked
+    }
+
+    fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) {
+        this.isInsecureLockscreenEnabled = isLockscreenEnabled
+    }
+
+    fun setBypassEnabled(isBypassEnabled: Boolean) {
+        this.isBypassEnabled = isBypassEnabled
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index a5f5d52..aa52609 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -63,9 +63,6 @@
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
-    private val _isKeyguardUnlocked = MutableStateFlow(false)
-    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
-
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
 
@@ -150,11 +147,6 @@
         return _isKeyguardShowing.value
     }
 
-    private var _isBypassEnabled = false
-    override fun isBypassEnabled(): Boolean {
-        return _isBypassEnabled
-    }
-
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
@@ -252,14 +244,6 @@
         _statusBarState.value = state
     }
 
-    fun setKeyguardUnlocked(isUnlocked: Boolean) {
-        _isKeyguardUnlocked.value = isUnlocked
-    }
-
-    fun setBypassEnabled(isEnabled: Boolean) {
-        _isBypassEnabled = isEnabled
-    }
-
     fun setScreenModel(screenModel: ScreenModel) {
         _screenModel.value = screenModel
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 2e3bb2b..1cae09b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,6 +19,7 @@
 
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -42,6 +43,7 @@
         sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
         repository: FakeKeyguardRepository = FakeKeyguardRepository(),
         commandQueue: FakeCommandQueue = FakeCommandQueue(),
+        deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
         configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
         shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -52,6 +54,7 @@
             commandQueue = commandQueue,
             featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
+            deviceEntryRepository = deviceEntryRepository,
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
             shadeRepository = shadeRepository,
@@ -60,6 +63,7 @@
                 commandQueue = commandQueue,
                 featureFlags = featureFlags,
                 sceneContainerFlags = sceneContainerFlags,
+                deviceEntryRepository = deviceEntryRepository,
                 bouncerRepository = bouncerRepository,
                 configurationRepository = configurationRepository,
                 shadeRepository = shadeRepository,
@@ -69,7 +73,7 @@
     }
 
     /** Provide defaults, otherwise tests will throw an error */
-    fun createFakeFeatureFlags(): FakeFeatureFlags {
+    private fun createFakeFeatureFlags(): FakeFeatureFlags {
         return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
     }
 
@@ -78,6 +82,7 @@
         val commandQueue: FakeCommandQueue,
         val featureFlags: FakeFeatureFlags,
         val sceneContainerFlags: SceneContainerFlags,
+        val deviceEntryRepository: FakeDeviceEntryRepository,
         val bouncerRepository: FakeKeyguardBouncerRepository,
         val configurationRepository: FakeConfigurationRepository,
         val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
new file mode 100644
index 0000000..f62bf60
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
+
+    var handleResult: Boolean = false
+    var policyResult: DisabledByPolicyInteractor.PolicyResult =
+        DisabledByPolicyInteractor.PolicyResult.TileEnabled
+
+    override suspend fun isDisabled(
+        userId: Int,
+        userRestriction: String?
+    ): DisabledByPolicyInteractor.PolicyResult = policyResult
+
+    override fun handlePolicyResult(
+        policyResult: DisabledByPolicyInteractor.PolicyResult
+    ): Boolean = handleResult
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 13437c9..1cb4ab7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import javax.annotation.CheckReturnValue
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 4e0266e..9c99cb5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.base.interactor
 
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 69c89e8..179206f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -34,6 +34,9 @@
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -73,16 +76,10 @@
     val testScope = TestScope(testDispatcher)
     val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
     val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
-    private val userRepository: UserRepository by lazy {
-        FakeUserRepository().apply {
-            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
-            setUserInfos(users)
-            runBlocking { setSelectedUserInfo(users.first()) }
-        }
-    }
-
+    val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
     val authenticationRepository: FakeAuthenticationRepository by lazy {
         FakeAuthenticationRepository(
+            deviceEntryRepository = deviceEntryRepository,
             currentTime = { testScope.currentTime },
         )
     }
@@ -103,6 +100,14 @@
     }
     val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
 
+    private val userRepository: UserRepository by lazy {
+        FakeUserRepository().apply {
+            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
+            setUserInfos(users)
+            runBlocking { setSelectedUserInfo(users.first()) }
+        }
+    }
+
     private val context = test.context
 
     private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
@@ -145,31 +150,41 @@
         )
     }
 
-    fun authenticationRepository(): FakeAuthenticationRepository {
-        return authenticationRepository
+    fun deviceEntryInteractor(
+        repository: DeviceEntryRepository = deviceEntryRepository,
+        authenticationInteractor: AuthenticationInteractor,
+        sceneInteractor: SceneInteractor,
+    ): DeviceEntryInteractor {
+        return DeviceEntryInteractor(
+            applicationScope = applicationScope(),
+            repository = repository,
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
     }
 
     fun authenticationInteractor(
-        repository: AuthenticationRepository,
-        sceneInteractor: SceneInteractor = sceneInteractor(),
+        repository: AuthenticationRepository = authenticationRepository,
     ): AuthenticationInteractor {
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
             repository = repository,
             backgroundDispatcher = testDispatcher,
             userRepository = userRepository,
-            keyguardRepository = keyguardRepository,
-            sceneInteractor = sceneInteractor,
+            deviceEntryRepository = deviceEntryRepository,
             clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
 
-    fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+    fun keyguardInteractor(
+        repository: KeyguardRepository = keyguardRepository
+    ): KeyguardInteractor {
         return KeyguardInteractor(
             repository = repository,
             commandQueue = FakeCommandQueue(),
             featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
+            deviceEntryRepository = FakeDeviceEntryRepository(),
             bouncerRepository = FakeKeyguardBouncerRepository(),
             configurationRepository = FakeConfigurationRepository(),
             shadeRepository = FakeShadeRepository(),
@@ -185,6 +200,7 @@
     }
 
     fun bouncerInteractor(
+        deviceEntryInteractor: DeviceEntryInteractor,
         authenticationInteractor: AuthenticationInteractor,
         sceneInteractor: SceneInteractor,
     ): BouncerInteractor {
@@ -192,6 +208,7 @@
             applicationScope = applicationScope(),
             applicationContext = context,
             repository = BouncerRepository(),
+            deviceEntryInteractor = deviceEntryInteractor,
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
             flags = sceneContainerFlags,
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 25f57b3..fb8db21 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -19,7 +19,10 @@
 java_library_static {
     name: "services.companion",
     defaults: ["platform_service_defaults"],
-    srcs: [":services.companion-sources"],
+    srcs: [
+        ":services.companion-sources",
+        ":VirtualCamera-aidl-sources",
+    ],
     libs: [
         "app-compat-annotations",
         "services.core",
@@ -29,3 +32,11 @@
         "virtualdevice_flags_lib",
     ],
 }
+
+filegroup {
+    name: "VirtualCamera-aidl-sources",
+    srcs: [
+        "java/com/android/server/companion/virtual/camera/*.aidl",
+    ],
+    path: "java",
+}
diff --git a/services/companion/OWNERS b/services/companion/OWNERS
index 734d8b6..dcf2377 100644
--- a/services/companion/OWNERS
+++ b/services/companion/OWNERS
@@ -1 +1,3 @@
-include /core/java/android/companion/OWNERS
\ No newline at end of file
+include /core/java/android/companion/OWNERS
+
+per-file Android.bp,lint-baseline.xml,OWNERS=file:java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 253ef43..3583a78 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -39,7 +39,7 @@
           "include-filter": "android.hardware.input.cts.tests"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ],
       "file_patterns": ["Virtual[^/]*\\.java"]
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index a2e4d2c..8a2aa61 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -50,6 +50,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.camera.IVirtualCamera;
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
@@ -105,6 +106,7 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -114,7 +116,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-
 final class VirtualDeviceImpl extends IVirtualDevice.Stub
         implements IBinder.DeathRecipient, RunningAppsChangedListener {
 
@@ -178,6 +179,8 @@
     private final InputController mInputController;
     private final SensorController mSensorController;
     private final CameraAccessController mCameraAccessController;
+    @Nullable // Null if virtual camera flag is off.
+    private final VirtualCameraController mVirtualCameraController;
     private VirtualAudioController mVirtualAudioController;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
@@ -270,7 +273,8 @@
                 soundEffectListener,
                 runningAppsChangedCallback,
                 params,
-                DisplayManagerGlobal.getInstance());
+                DisplayManagerGlobal.getInstance(),
+                Flags.virtualCamera() ? new VirtualCameraController(context) : null);
     }
 
     @VisibleForTesting
@@ -289,7 +293,8 @@
             IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params,
-            DisplayManagerGlobal displayManager) {
+            DisplayManagerGlobal displayManager,
+            VirtualCameraController virtualCameraController) {
         super(PermissionEnforcer.fromContext(context));
         mVirtualDeviceLog = virtualDeviceLog;
         mOwnerPackageName = attributionSource.getPackageName();
@@ -324,6 +329,7 @@
         } else {
             mPermissionDialogComponent = null;
         }
+        mVirtualCameraController = virtualCameraController;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -564,6 +570,9 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+        if (mVirtualCameraController != null) {
+            mVirtualCameraController.close();
+        }
     }
 
     @Override
@@ -920,24 +929,37 @@
         }
     }
 
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void registerVirtualCamera(@NonNull IVirtualCamera camera) {
+        super.registerVirtualCamera_enforcePermission();
+        if (mVirtualCameraController == null) {
+            return;
+        }
+        mVirtualCameraController.registerCamera(Objects.requireNonNull(camera));
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        String indent = "    ";
         fout.println("  VirtualDevice: ");
-        fout.println("    mDeviceId: " + mDeviceId);
-        fout.println("    mAssociationId: " + mAssociationInfo.getId());
-        fout.println("    mOwnerPackageName: " + mOwnerPackageName);
-        fout.println("    mParams: ");
-        mParams.dump(fout, "        ");
-        fout.println("    mVirtualDisplayIds: ");
+        fout.println(indent + "mDeviceId: " + mDeviceId);
+        fout.println(indent + "mAssociationId: " + mAssociationInfo.getId());
+        fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName);
+        fout.println(indent + "mParams: ");
+        mParams.dump(fout, indent + indent);
+        fout.println(indent + "mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
             fout.println("    mDevicePolicies: " + mDevicePolicies);
             for (int i = 0; i < mVirtualDisplays.size(); i++) {
-                fout.println("      " + mVirtualDisplays.keyAt(i));
+                fout.println(indent + "  " + mVirtualDisplays.keyAt(i));
             }
-            fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
+            fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
         mInputController.dump(fout);
         mSensorController.dump(fout);
+        if (mVirtualCameraController != null) {
+            mVirtualCameraController.dump(fout, indent);
+        }
     }
 
     @GuardedBy("mVirtualDeviceLock")
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
new file mode 100644
index 0000000..a4c1c42
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+
+/**
+ * AIDL Interface to communicate with the VirtualCamera HAL
+ * @hide
+ */
+interface IVirtualCameraService {
+
+    /**
+     * Registers a new camera with the virtual camera hal.
+     * @return true if the camera was successfully registered
+     */
+    boolean registerCamera(in IVirtualCamera camera);
+
+    /**
+     * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't
+     * be visible to the camera framework anymore.
+     */
+    void unregisterCamera(in IVirtualCamera camera);
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
new file mode 100644
index 0000000..031d949
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages the registration and removal of virtual camera from the server side.
+ *
+ * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service
+ * to be up and running
+ */
+public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection {
+
+    private static class VirtualCameraInfo {
+
+        private final IVirtualCamera mVirtualCamera;
+        private boolean mIsRegistered;
+
+        VirtualCameraInfo(IVirtualCamera virtualCamera) {
+            mVirtualCamera = virtualCamera;
+        }
+    }
+
+    private static final String TAG = "VirtualCameraController";
+
+    private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera";
+    private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService";
+    private final Context mContext;
+
+    @Nullable private IVirtualCameraService mVirtualCameraService = null;
+
+    @GuardedBy("mCameras")
+    private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1);
+
+    public VirtualCameraController(Context context) {
+        mContext = context;
+        connectVirtualCameraService();
+    }
+
+    private void connectVirtualCameraService() {
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent();
+            intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE);
+            intent.setComponent(
+                    ComponentName.createRelative(
+                            VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS));
+            mContext.startServiceAsUser(intent, UserHandle.SYSTEM);
+            if (!mContext.bindServiceAsUser(
+                    intent,
+                    this,
+                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+                    UserHandle.SYSTEM)) {
+                mContext.unbindService(this);
+                Log.w(
+                        TAG,
+                        "connectVirtualCameraService: Failed to connect to the virtual camera "
+                                + "service");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    private void forwardPendingRegistrations() {
+        IVirtualCameraService cameraService = mVirtualCameraService;
+        if (cameraService == null) {
+            return;
+        }
+        synchronized (mCameras) {
+            for (VirtualCameraInfo cameraInfo : mCameras.values()) {
+                if (cameraInfo.mIsRegistered) {
+                    continue;
+                }
+                try {
+                    cameraService.registerCamera(cameraInfo.mVirtualCamera);
+                    cameraInfo.mIsRegistered = true;
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove the virtual camera with the provided name
+     *
+     * @param camera The name of the camera to remove
+     */
+    public void unregisterCamera(@NonNull IVirtualCamera camera) {
+        IVirtualCameraService virtualCameraService = mVirtualCameraService;
+        if (virtualCameraService != null) {
+            try {
+                virtualCameraService.unregisterCamera(camera);
+                synchronized (mCameras) {
+                    VirtualCameraInfo cameraInfo = mCameras.remove(camera);
+                    cameraInfo.mIsRegistered = false;
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Register a new virtual camera with the provided characteristics.
+     *
+     * @param camera The {@link IVirtualCamera} producing the image to communicate with the client.
+     * @throws IllegalArgumentException if the characteristics could not be parsed.
+     */
+    public void registerCamera(@NonNull IVirtualCamera camera) {
+        IVirtualCameraService service = mVirtualCameraService;
+        VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera);
+        synchronized (mCameras) {
+            mCameras.put(camera, virtualCameraInfo);
+        }
+        if (service != null) {
+            try {
+                if (service.registerCamera(camera)) {
+                    virtualCameraInfo.mIsRegistered = true;
+                    return;
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+
+        // Service was not available or registration failed, save the registration for later
+        connectVirtualCameraService();
+    }
+
+    @Override
+    public void binderDied() {
+        Log.d(TAG, "binderDied");
+        mVirtualCameraService = null;
+    }
+
+    @Override
+    public void onBindingDied(ComponentName name) {
+        mVirtualCameraService = null;
+        Log.d(TAG, "onBindingDied() called with: name = [" + name + "]");
+    }
+
+    @Override
+    public void onNullBinding(ComponentName name) {
+        mVirtualCameraService = null;
+        Log.d(TAG, "onNullBinding() called with: name = [" + name + "]");
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        Log.d(TAG, "onServiceConnected: " + name.toString());
+        mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service);
+        try {
+            service.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        forwardPendingRegistrations();
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]");
+        mVirtualCameraService = null;
+    }
+
+    /** Release resources associated with this controller. */
+    public void close() {
+        if (mVirtualCameraService == null) {
+            return;
+        }
+        synchronized (mCameras) {
+            mCameras.forEach(
+                    (name, cameraInfo) -> {
+                        try {
+                            mVirtualCameraService.unregisterCamera(name);
+                        } catch (RemoteException e) {
+                            Log.w(
+                                    TAG,
+                                    "close(): Camera failed to be removed on camera service.",
+                                    e);
+                        }
+                    });
+        }
+        mContext.unbindService(this);
+    }
+
+    /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
+    public void dump(PrintWriter fout, String indent) {
+        fout.println(indent + "VirtualCameraController:");
+        indent += indent;
+        fout.printf("%sService:%s\n", indent, mVirtualCameraService);
+        synchronized (mCameras) {
+            fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
+            for (VirtualCameraInfo info : mCameras.values()) {
+                VirtualCameraHalConfig config = null;
+                try {
+                    config = info.mVirtualCamera.getHalConfig();
+                } catch (RemoteException ex) {
+                    Log.w(TAG, ex);
+                }
+                fout.printf(
+                        "%s- %s isRegistered: %s, token: %s\n",
+                        indent,
+                        config == null ? "" : config.displayName,
+                        info.mIsRegistered,
+                        info.mVirtualCamera);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 2a46d86..a1e6b58f 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -81,7 +81,7 @@
      * Returns whether an intent matches the IntentFilter with a pre-resolved type.
      */
     public static boolean intentMatchesFilter(
-            IntentFilter filter, Intent intent, String resolvedType) {
+            IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) {
         final boolean debug = localLOGV
                 || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
 
@@ -94,9 +94,13 @@
             filter.dump(logPrinter, "  ");
         }
 
-        final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
+        int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
                 intent.getData(), intent.getCategories(), TAG);
 
+        if (match >= 0 && defaultOnly && !filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
+            match = IntentFilter.NO_MATCH_CATEGORY;
+        }
+
         if (match >= 0) {
             if (debug) {
                 Slog.v(TAG, "Filter matched!  match=0x" + Integer.toHexString(match));
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index d6f1348..ced7773 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -342,7 +342,10 @@
 
             // Check if user is associated with the subscription
             if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                    Binder.getCallingUserHandle())) {
+                    Binder.getCallingUserHandle())
+                    // For inactive sub, fall through to MMS service to have it recorded in metrics.
+                    && isActiveSubId(subId)) {
+                // Try remind user to use another profile to send.
                 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext,
                         subId, Binder.getCallingUid(), callingPkg);
                 return;
@@ -550,6 +553,17 @@
         }
     }
 
+    /** @return true if the subId is active. */
+    private boolean isActiveSubId(int subId) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
+            return subManager != null && subManager.isActiveSubscriptionId(subId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private int getPhoneIdFromSubId(int subId) {
         SubscriptionManager subManager = (SubscriptionManager)
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e17424b..6e984bb 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,6 +250,7 @@
         private Monitor mCurrentMonitor;
         private long mStartTimeMillis;
         private int mPauseCount;
+        private long mOneOffTimeoutMillis;
 
         HandlerChecker(Handler handler, String name) {
             mHandler = handler;
@@ -269,7 +270,13 @@
          * @param handlerCheckerTimeoutMillis the timeout to use for this run
          */
         public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
-            mWaitMaxMillis = handlerCheckerTimeoutMillis;
+            if (mOneOffTimeoutMillis > 0) {
+              mWaitMaxMillis = mOneOffTimeoutMillis;
+              mOneOffTimeoutMillis = 0;
+            } else {
+              mWaitMaxMillis = handlerCheckerTimeoutMillis;
+            }
+
             if (mCompleted) {
                 // Safe to update monitors in queue, Handler is not in the middle of work
                 mMonitors.addAll(mMonitorQueue);
@@ -352,6 +359,23 @@
             }
         }
 
+        /**
+         * Sets the timeout of the HandlerChecker for one run.
+         *
+         * <p>The current run will be ignored and the next run will be set to this timeout.
+         *
+         * <p>If a one off timeout is already set, the maximum timeout will be used.
+         */
+        public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
+            mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+            // Mark as completed, because there's a chance we called this after the watchog
+            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
+            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            mCompleted = true;
+            Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
+                    + reason + ". New timeout: " + mOneOffTimeoutMillis);
+        }
+
         /** Pause the HandlerChecker. */
         public void pauseLocked(String reason) {
             mPauseCount++;
@@ -598,6 +622,27 @@
         }
     }
 
+     /**
+     * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+     * to run a slow operation on one of the monitored thread.
+     *
+     * <p>After the next run, the timeout will go back to the default value.
+     *
+     * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
+     *
+     * <p>If a one-off timeout for the current thread is already, the max value will be used.
+     */
+    public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+        synchronized (mLock) {
+            for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
+                HandlerChecker checker = hc.checker();
+                if (Thread.currentThread().equals(checker.getThread())) {
+                    checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+                }
+            }
+        }
+    }
+
     /**
      * Pauses Watchdog action for the currently running thread. Useful before executing long running
      * operations that could falsely trigger the watchdog. Each call to this will require a matching
@@ -609,7 +654,7 @@
      * adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
      *
      * <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
-     * pauses have been resumed.
+     * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
      */
     public void pauseWatchingCurrentThread(String reason) {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 2c83c6f..6cca130 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -150,16 +150,7 @@
     public boolean isChangeEnabledByUid(long changeId, int uid) {
         super.isChangeEnabledByUid_enforcePermission();
 
-        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
-        if (packages == null || packages.length == 0) {
-            return mCompatConfig.defaultChangeIdValue(changeId);
-        }
-        boolean enabled = true;
-        for (String packageName : packages) {
-            enabled &= isChangeEnabledByPackageName(changeId, packageName,
-                    UserHandle.getUserId(uid));
-        }
-        return enabled;
+        return isChangeEnabledByUidInternal(changeId, uid);
     }
 
     /**
@@ -208,6 +199,25 @@
         return false;
     }
 
+    /**
+     * Internal version of {@link #isChangeEnabledByUid(long, int)}.
+     *
+     * <p>Does not perform costly permission check.
+     */
+    public boolean isChangeEnabledByUidInternal(long changeId, int uid) {
+        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length == 0) {
+            return mCompatConfig.defaultChangeIdValue(changeId);
+        }
+        boolean enabled = true;
+        final int userId = UserHandle.getUserId(uid);
+        for (String packageName : packages) {
+            final var appInfo = getApplicationInfo(packageName, userId);
+            enabled &= isChangeEnabledInternal(changeId, appInfo);
+        }
+        return enabled;
+    }
+
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e942c17..d372f30 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -142,6 +142,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -548,6 +549,9 @@
     @VisibleForTesting
     DisplayManagerService(Context context, Injector injector) {
         super(context);
+        FoldSettingProvider foldSettingProvider = new FoldSettingProvider(context,
+                new SettingsWrapper(),
+                new FoldLockSettingAvailabilityProvider(context.getResources()));
         mInjector = injector;
         mContext = context;
         mFlags = injector.getFlags();
@@ -555,8 +559,8 @@
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
-                new FoldSettingProvider(mContext, new SettingsWrapper()), mDisplayDeviceRepo,
-                new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
+                foldSettingProvider,
+                mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 6f243e1..049b2fd 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -4,7 +4,6 @@
             "name": "DisplayServiceTests",
             "options": [
                 {"include-filter": "com.android.server.display"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"},
                 {"exclude-annotation": "org.junit.Ignore"}
             ]
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index e46edd9..652e6cf 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,6 +24,7 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.BrightnessUtils;
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
@@ -178,8 +179,12 @@
                 debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
                 transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
             }
+
+            float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
+            float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
+
             mDesiredTransitionRate = Math.abs(
-                    (mMaxBrightness - mDesiredMaxBrightness) * 1000f / transitionDuration);
+                    (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
 
             mHandler.removeCallbacks(mDebouncer);
             mHandler.postDelayed(mDebouncer, debounceTime);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 41053e9..68848a2 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -16,36 +16,66 @@
 
 package com.android.server.grammaticalinflection;
 
+import static android.app.Flags.systemTermsOfAddressEnabled;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
 import android.annotation.Nullable;
+import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
-import android.os.IBinder;
+import android.os.Environment;
 import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.util.AtomicFile;
 import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.Xml;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
 /**
  * The implementation of IGrammaticalInflectionManager.aidl.
  *
  * <p>This service is API entry point for storing app-specific grammatical inflection.
  */
 public class GrammaticalInflectionService extends SystemService {
-    private final String TAG = "GrammaticalInflection";
-    private final GrammaticalInflectionBackupHelper mBackupHelper;
-    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
-    private PackageManagerInternal mPackageManagerInternal;
+    private static final String TAG = "GrammaticalInflection";
+    private static final String ATTR_NAME = "grammatical_gender";
+    private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml";
+    private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
     private static final String GRAMMATICAL_INFLECTION_ENABLED =
             "i18n.grammatical_Inflection.enabled";
 
+    private final GrammaticalInflectionBackupHelper mBackupHelper;
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    private final Object mLock = new Object();
+    private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray();
+
+    private PackageManagerInternal mPackageManagerInternal;
+    private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+
     /**
      * Initializes the system service.
      * <p>
@@ -62,22 +92,46 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mBackupHelper = new GrammaticalInflectionBackupHelper(
                 this, context.getPackageManager());
+        mBinderService = new GrammaticalInflectionBinderService();
     }
 
     @Override
     public void onStart() {
-        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService);
         LocalServices.addService(GrammaticalInflectionManagerInternal.class,
                 new GrammaticalInflectionManagerInternalImpl());
     }
 
-    private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+    private final class GrammaticalInflectionBinderService extends
+            IGrammaticalInflectionManager.Stub {
         @Override
         public void setRequestedApplicationGrammaticalGender(
                 String appPackageName, int userId, int gender) {
             GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
                     appPackageName, userId, gender);
         }
+
+        @Override
+        public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+            checkCallerIsSystem();
+            checkSystemTermsOfAddressIsEnabled();
+            GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
+                    userId);
+        }
+
+        @Override
+        public int getSystemGrammaticalGender(int userId) {
+            checkSystemTermsOfAddressIsEnabled();
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            (new GrammaticalInflectionShellCommand(mBinderService))
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
     };
 
     private final class GrammaticalInflectionManagerInternalImpl
@@ -94,12 +148,6 @@
         public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
             mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
         }
-
-        private void checkCallerIsSystem() {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                throw new SecurityException("Caller is not system.");
-            }
-        }
     }
 
     protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -137,4 +185,105 @@
 
         updater.setGrammaticalGender(gender).commit();
     }
+
+    protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+        if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        synchronized (mLock) {
+            final File file = getGrammaticalGenderFile(userId);
+            final AtomicFile atomicFile = new AtomicFile(file);
+            FileOutputStream stream = null;
+            try {
+                stream = atomicFile.startWrite();
+                stream.write(toXmlByteArray(grammaticalGender, stream));
+                atomicFile.finishWrite(stream);
+                mGrammaticalGenderCache.put(userId, grammaticalGender);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to write file " + atomicFile, e);
+                if (stream != null) {
+                    atomicFile.failWrite(stream);
+                }
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
+    //  gender.
+    public int getSystemGrammaticalGender(int userId) {
+        synchronized (mLock) {
+            final File file = getGrammaticalGenderFile(userId);
+            if (!file.exists()) {
+                Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+                return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            }
+
+            if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+                try {
+                    InputStream in = new FileInputStream(file);
+                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                    mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+                } catch (IOException | XmlPullParserException e) {
+                    Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+                }
+            }
+            return mGrammaticalGenderCache.get(userId);
+        }
+    }
+
+    private File getGrammaticalGenderFile(int userId) {
+        final File dir = new File(Environment.getDataSystemCeDirectory(userId),
+                TAG_GRAMMATICAL_INFLECTION);
+        return new File(dir, USER_SETTINGS_FILE_NAME);
+    }
+
+    private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
+
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            out.startDocument(/* encoding= */ null, /* standalone= */ true);
+            out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+            out.attributeInt(null, ATTR_NAME, grammaticalGender);
+            out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+            out.endDocument();
+
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+
+        XmlUtils.nextElement(parser);
+        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+            String tagName = parser.getName();
+            if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) {
+                return parser.getAttributeInt(null, ATTR_NAME);
+            } else {
+                XmlUtils.nextElement(parser);
+            }
+        }
+
+        return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+    }
+
+    private void checkCallerIsSystem() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
+            throw new SecurityException("Caller is not system and shell.");
+        }
+    }
+
+    private void checkSystemTermsOfAddressIsEnabled() {
+        if (!systemTermsOfAddressEnabled()) {
+            throw new RuntimeException("The flag must be enabled to allow calling the API.");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
new file mode 100644
index 0000000..d223728
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.ActivityManager;
+import android.app.GrammaticalInflectionManager;
+import android.app.IGrammaticalInflectionManager;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link GrammaticalInflectionService}
+ */
+class GrammaticalInflectionShellCommand extends ShellCommand {
+
+    private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>();
+    static {
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                "Not specified (0)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)");
+    }
+
+    private final IGrammaticalInflectionManager mBinderService;
+
+    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+        mBinderService = grammaticalInflectionManager;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "set-system-grammatical-gender":
+                return runSetSystemWideGrammaticalGender();
+            case "get-system-grammatical-gender":
+                return runGetSystemGrammaticalGender();
+            default: {
+                return handleDefaultCommands(cmd);
+            }
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println(
+                "  set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender "
+                        + "<GRAMMATICAL_GENDER>]");
+        pw.println("      Set the system grammatical gender for system.");
+        pw.println("      --user <USER_ID>: apply for the given user, "
+                + "the current user is used when unspecified.");
+        pw.println(
+                "      --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user "
+                        + "preferred in system, not specified (0) is used when unspecified.");
+        pw.println(
+                "                 eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine"
+                        + ".");
+        pw.println(
+                "  get-system-grammatical-gender [--user <USER_ID>]");
+        pw.println("      Get the system grammatical gender for system.");
+        pw.println("      --user <USER_ID>: apply for the given user, "
+                + "the current user is used when unspecified.");
+    }
+
+    private int runSetSystemWideGrammaticalGender() {
+        int userId = ActivityManager.getCurrentUser();
+        int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        do {
+            String option = getNextOption();
+            if (option == null) {
+                break;
+            }
+            switch (option) {
+                case "--user": {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                }
+                case "-g":
+                case "--grammaticalGender": {
+                    grammaticalGender = parseGrammaticalGender();
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + option);
+                }
+            }
+        } while (true);
+
+        try {
+            mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+        } catch (RemoteException e) {
+            getOutPrintWriter().println("Remote Exception: " + e);
+        }
+        return 0;
+    }
+
+    private int runGetSystemGrammaticalGender() {
+        int userId = ActivityManager.getCurrentUser();
+        do {
+            String option = getNextOption();
+            if (option == null) {
+                break;
+            }
+            switch (option) {
+                case "--user": {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + option);
+                }
+            }
+        } while (true);
+
+        try {
+            int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+            getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
+        } catch (RemoteException e) {
+            getOutPrintWriter().println("Remote Exception: " + e);
+        }
+        return 0;
+    }
+
+    private int parseGrammaticalGender() {
+        String arg = getNextArg();
+        if (arg == null) {
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        } else {
+            int grammaticalGender = Integer.parseInt(arg);
+            if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                    grammaticalGender)) {
+                return grammaticalGender;
+            } else {
+                return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
index 5f16ba9..41d079e 100644
--- a/services/core/java/com/android/server/grammaticalinflection/OWNERS
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -2,3 +2,4 @@
 allenwtsu@google.com
 goldmanj@google.com
 calvinpan@google.com
+zoeychen@google.com
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
index f868ea0..17b98ce8 100644
--- a/services/core/java/com/android/server/lights/TEST_MAPPING
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -4,16 +4,14 @@
       "name": "CtsHardwareTestCases",
       "options": [
         {"include-filter": "com.android.hardware.lights"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"}
       ]
     },
     {
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.lights"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index b881b44..ddf3d76 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -7,7 +7,7 @@
                     "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
                 },
                 {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
                 }
             ]
         }
@@ -20,7 +20,7 @@
                     "include-filter": "com.android.server.locksettings."
                 },
                 {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
                 }
             ]
         }
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
index f4b13a0..9041552 100644
--- a/services/core/java/com/android/server/logcat/TEST_MAPPING
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.logcat"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index a792498..7aa9118 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "MediaProjectionTests",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index 7db2e8b..468c451 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "CtsNotificationTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
@@ -14,9 +11,6 @@
         },
         {
           "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
         }
       ]
     },
@@ -24,9 +18,6 @@
       "name": "FrameworksUiServicesTests",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
@@ -34,9 +25,6 @@
         },
         {
           "exclude-annotation": "androidx.test.filters.LargeTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
         }
       ]
     }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 6baa889..5d2944e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -580,7 +580,7 @@
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                             mInjector.getCompatibility(), mComponentResolver,
-                            list, false, intent, resolvedType, filterCallingUid);
+                            list, false, intent, resolvedType, flags, filterCallingUid);
                 }
             }
         } else {
@@ -610,7 +610,7 @@
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                     mInjector.getCompatibility(), mComponentResolver,
-                    list, false, originalIntent, resolvedType, filterCallingUid);
+                    list, false, originalIntent, resolvedType, flags, filterCallingUid);
         }
 
         return skipPostResolution ? list : applyPostResolutionFilter(
@@ -699,7 +699,7 @@
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                             mInjector.getCompatibility(), mComponentResolver,
-                            list, false, intent, resolvedType, callingUid);
+                            list, false, intent, resolvedType, flags, callingUid);
                 }
             }
         } else {
@@ -711,7 +711,7 @@
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                     mInjector.getCompatibility(), mComponentResolver,
-                    list, false, originalIntent, resolvedType, callingUid);
+                    list, false, originalIntent, resolvedType, flags, callingUid);
         }
 
         return list;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 38f241d..8e91f42 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -44,6 +44,7 @@
 import android.app.ActivityManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -69,6 +70,7 @@
 import android.os.FileUtils;
 import android.os.Process;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
 import android.os.incremental.IncrementalStorage;
 import android.os.incremental.V4Signature;
@@ -180,8 +182,9 @@
     public @interface SharedUserIdJoinType {}
 
     /**
-     * Components of apps targeting Android T and above will stop receiving intents from
-     * external callers that do not match its declared intent filters.
+     * Intents sent from apps targeting Android V and above will stop resolving to components with
+     * non matching intent filters, even when explicitly setting a component name, unless the
+     * target components are in the same app as the calling app.
      *
      * When an app registers an exported component in its manifest and adds an <intent-filter>,
      * the component can be started by any intent - even those that do not match the intent filter.
@@ -189,8 +192,9 @@
      * Without checking the intent when the component is started, in some circumstances this can
      * allow 3P apps to trigger internal-only functionality.
      */
+    @Overridable
     @ChangeId
-    @Disabled  /* Revert enforcement: b/274147456 */
+    @Disabled  /* Enforcement reverted in T: b/274147456 */
     private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
 
     /**
@@ -1187,19 +1191,27 @@
     public static void applyEnforceIntentFilterMatching(
             PlatformCompat compat, ComponentResolverApi resolver,
             List<ResolveInfo> resolveInfos, boolean isReceiver,
-            Intent intent, String resolvedType, int filterCallingUid) {
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
+            int filterCallingUid) {
         if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
 
+        // Do not enforce filter matching when the caller is system or root
+        if (ActivityManager.canAccessUnexportedComponents(filterCallingUid)) return;
+
         final Printer logPrinter = DEBUG_INTENT_MATCHING
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
 
+        final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+
+        final boolean enforce = compat.isChangeEnabledByUidInternal(
+                ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
+
         for (int i = resolveInfos.size() - 1; i >= 0; --i) {
             final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
 
-            // Do not enforce filter matching when the caller is system, root, or the same app
-            if (ActivityManager.checkComponentPermission(null, filterCallingUid,
-                    info.applicationInfo.uid, false) == PackageManager.PERMISSION_GRANTED) {
+            // Skip filter matching when the caller is targeting the same app
+            if (UserHandle.isSameApp(filterCallingUid, info.applicationInfo.uid)) {
                 continue;
             }
 
@@ -1221,14 +1233,11 @@
                 continue;
             }
 
-            // Only enforce filter matching if target app's target SDK >= T
-            final boolean enforce = compat.isChangeEnabledInternal(
-                    ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo);
-
             boolean match = false;
             for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
                 IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
-                if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
+                if (IntentResolver.intentMatchesFilter(
+                        intentFilter, intent, resolvedType, defaultOnly)) {
                     match = true;
                     break;
                 }
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 160b2aa..da14397 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -459,7 +459,7 @@
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                             mPlatformCompat, componentResolver, list, true, intent,
-                            resolvedType, filterCallingUid);
+                            resolvedType, flags, filterCallingUid);
                 }
             }
         } else {
@@ -485,7 +485,7 @@
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                     mPlatformCompat, componentResolver,
-                    list, true, originalIntent, resolvedType, filterCallingUid);
+                    list, true, originalIntent, resolvedType, flags, filterCallingUid);
         }
 
         return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 390c45b..0fd7a37 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -55,6 +55,7 @@
     },
     {
       "name": "GtsContentTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "include-filter": "com.google.android.content.gts"
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 819a82c..338b479 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -32,7 +32,7 @@
       "name": "CtsPermissionPolicyTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
           "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
@@ -49,7 +49,7 @@
       "name": "CtsPermissionTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
           "include-filter": "android.permission.cts.SplitPermissionTest"
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4a4214f..dfbcbae6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3347,6 +3347,8 @@
             } else {
                 startDreaming = false;
             }
+            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming
+                    + " wakefulness=" + wakefulnessToString(wakefulness));
         }
 
         // Start dreaming if needed.
@@ -3381,19 +3383,23 @@
             if (startDreaming && isDreaming) {
                 mDreamsBatteryLevelDrain = 0;
                 if (wakefulness == WAKEFULNESS_DOZING) {
-                    Slog.i(TAG, "Dozing...");
+                    Slog.i(TAG, "Dozing powerGroup " + groupId);
                 } else {
-                    Slog.i(TAG, "Dreaming...");
+                    Slog.i(TAG, "Dreaming powerGroup " + groupId);
                 }
             }
 
             // If preconditions changed, wait for the next iteration to determine
             // whether the dream should continue (or be restarted).
             final PowerGroup powerGroup = mPowerGroups.get(groupId);
+            final int newWakefulness = powerGroup.getWakefulnessLocked();
             if (powerGroup.isSandmanSummonedLocked()
-                    || powerGroup.getWakefulnessLocked() != wakefulness) {
+                    || newWakefulness != wakefulness) {
                 return; // wait for next cycle
             }
+            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming
+                    + " wakefulness=" + newWakefulness);
+
 
             // Determine whether the dream should continue.
             long now = mClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index 19086a1..05a0e85 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -3,16 +3,14 @@
     {
       "name": "CtsBatterySavingTestCases",
       "options": [
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"}
       ]
     },
     {
       "name": "FrameworksMockingServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     },
@@ -20,7 +18,6 @@
       "name": "PowerServiceTests",
       "options": [
         {"include-filter": "com.android.server.power"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 88eaafa..3fd8323 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2115,7 +2115,7 @@
         IStatusBar bar = mBar;
         if (bar != null) {
             try {
-                bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+                bar.requestAddTile(callingUid, componentName, appName, label, icon, proxyCallback);
                 return;
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
diff --git a/services/core/java/com/android/server/utils/FoldSettingProvider.java b/services/core/java/com/android/server/utils/FoldSettingProvider.java
index d62628b..552343a 100644
--- a/services/core/java/com/android/server/utils/FoldSettingProvider.java
+++ b/services/core/java/com/android/server/utils/FoldSettingProvider.java
@@ -22,7 +22,7 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.internal.R;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
 import com.android.internal.util.SettingsWrapper;
 
 import java.util.Set;
@@ -51,14 +51,14 @@
     private static final String TAG = "FoldSettingProvider";
 
     private final ContentResolver mContentResolver;
-    private final boolean mIsFoldLockBehaviorAvailable;
     private final SettingsWrapper mSettingsWrapper;
+    private final FoldLockSettingAvailabilityProvider mFoldLockSettingAvailabilityProvider;
 
-    public FoldSettingProvider(Context context, SettingsWrapper settingsWrapper) {
+    public FoldSettingProvider(Context context, SettingsWrapper settingsWrapper,
+            FoldLockSettingAvailabilityProvider foldLockSettingAvailabilityProvider) {
         mContentResolver = context.getContentResolver();
         mSettingsWrapper = settingsWrapper;
-        mIsFoldLockBehaviorAvailable = context.getResources().getBoolean(
-                R.bool.config_fold_lock_behavior);
+        mFoldLockSettingAvailabilityProvider = foldLockSettingAvailabilityProvider;
     }
 
     /**
@@ -83,7 +83,9 @@
     }
 
     private String getFoldSettingValue() {
-        if (!mIsFoldLockBehaviorAvailable) {
+        boolean isFoldLockBehaviorAvailable =
+                mFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable();
+        if (!isFoldLockBehaviorAvailable) {
             return SETTING_VALUE_DEFAULT;
         }
         String foldSettingValue = mSettingsWrapper.getStringForUser(
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4c525e9..01ea33f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -28,6 +28,7 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
@@ -74,6 +75,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
+import android.multiuser.Flags;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.FileObserver;
@@ -114,6 +116,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -132,6 +135,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -746,6 +750,8 @@
     }
 
     private final Context mContext;
+    private final AtomicBoolean mIsInitialBinding = new AtomicBoolean(true);
+    private final ServiceThread mHandlerThread;
     private final WindowManagerInternal mWindowManagerInternal;
     private final PackageManagerInternal mPackageManagerInternal;
     private final IPackageManager mIPackageManager;
@@ -1619,6 +1625,12 @@
     public WallpaperManagerService(Context context) {
         if (DEBUG) Slog.v(TAG, "WallpaperService startup");
         mContext = context;
+        if (Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
+            mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
+            mHandlerThread.start();
+        } else {
+            mHandlerThread = null;
+        }
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
@@ -3652,7 +3664,10 @@
                     com.android.internal.R.bool.config_wallpaperTopApp)) {
                 bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
             }
-            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
+            Handler handler = Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()
+                    && !mIsInitialBinding.compareAndSet(true, false)
+                    ? mHandlerThread.getThreadHandler() : mContext.getMainThreadHandler();
+            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, handler,
                     new UserHandle(serviceUserId));
             if (!bindSuccess) {
                 String msg = "Unable to bind service: " + componentName;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 442269a..10405ec 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -302,12 +302,9 @@
         }
 
         // Always update the reordering time when this is called to ensure that the timeout
-        // is reset.  Extend this duration when running in tests.
-        final long timeout = ActivityManager.isRunningInUserTestHarness()
-                ? mFreezeTaskListTimeoutMs * 10
-                : mFreezeTaskListTimeoutMs;
+        // is reset
         mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
-        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
+        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2a33918..ea5c9c2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2234,9 +2234,11 @@
             mService.getTaskChangeNotificationController().notifyActivityUnpinned();
         }
         mWindowManager.mPolicy.setPipVisibilityLw(inPip);
-        mWmService.mTransactionFactory.get()
-                .setTrustedOverlay(task.getSurfaceControl(), inPip)
-                .apply();
+        if (task.getSurfaceControl() != null) {
+            mWmService.mTransactionFactory.get()
+                    .setTrustedOverlay(task.getSurfaceControl(), inPip)
+                    .apply();
+        }
     }
 
     void executeAppTransitionForAllDisplay() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 261fc2e..8385615 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1162,11 +1162,17 @@
                 forAllActivities(oldParentTask::cleanUpActivityReferences);
             }
 
-            if (oldParent.inPinnedWindowingMode()
-                    && (newParent == null || !newParent.inPinnedWindowingMode())) {
-                // Notify if a task from the root pinned task is being removed
-                // (or moved depending on the mode).
-                mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+            if (newParent == null || !newParent.inPinnedWindowingMode()) {
+                if (oldParent.inPinnedWindowingMode()) {
+                    // Notify if a task from the root pinned task is being removed
+                    // (or moved depending on the mode).
+                    mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+                } else if (inPinnedWindowingMode()) {
+                    // The task in pinned mode is removed, even though the old parent was not pinned
+                    // The task was most likely force killed or crashed
+                    Slog.e(TAG, "Pinned task is removed t=" + this);
+                    mRootWindowContainer.notifyActivityPipModeChanged(this, null);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d95bc7..50bc825 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -255,6 +255,11 @@
     boolean mClearedForReorderActivityToFront;
 
     /**
+     * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}.
+     */
+    boolean mIsSurfaceManagedBySystemOrganizer = false;
+
+    /**
      * When we are in the process of pausing an activity, before starting the
      * next one, this variable holds the activity that is currently being paused.
      *
@@ -449,13 +454,21 @@
 
     void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
             @NonNull String processName) {
+        setTaskFragmentOrganizer(organizer, uid, processName,
+                false /* isSurfaceManagedBySystemOrganizer */);
+    }
+
+    void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+            @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) {
         mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
         mTaskFragmentOrganizerUid = uid;
         mTaskFragmentOrganizerProcessName = processName;
+        mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer;
     }
 
     void onTaskFragmentOrganizerRemoved() {
         mTaskFragmentOrganizer = null;
+        mIsSurfaceManagedBySystemOrganizer = false;
     }
 
     /** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2396,6 +2409,9 @@
         if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
             return;
         }
+        if (mIsSurfaceManagedBySystemOrganizer) {
+            return;
+        }
         if (mTransitionController.isShellTransitionsEnabled()
                 && !mTransitionController.isCollecting(this)) {
             // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index ea722b6..04164c2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -26,6 +26,7 @@
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
 
@@ -50,12 +51,15 @@
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
 import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -133,6 +137,13 @@
                 new WeakHashMap<>();
 
         /**
+         * Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true,
+         * the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the
+         * client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event.
+         */
+        private final boolean mIsSystemOrganizer;
+
+        /**
          * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
          * organized by this organizer.
          */
@@ -147,10 +158,12 @@
          */
         private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
 
-        TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
+        TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
+                boolean isSystemOrganizer) {
             mOrganizer = organizer;
             mOrganizerPid = pid;
             mOrganizerUid = uid;
+            mIsSystemOrganizer = isSystemOrganizer;
             try {
                 mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
             } catch (RemoteException e) {
@@ -235,11 +248,15 @@
             tf.mTaskFragmentAppearedSent = true;
             mLastSentTaskFragmentInfos.put(tf, info);
             mTaskFragmentTaskIds.put(tf, taskId);
-            return new TaskFragmentTransaction.Change(
+            final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change(
                     TYPE_TASK_FRAGMENT_APPEARED)
                     .setTaskFragmentToken(tf.getFragmentToken())
                     .setTaskFragmentInfo(info)
                     .setTaskId(taskId);
+            if (mIsSystemOrganizer) {
+                change.setTaskFragmentSurfaceControl(tf.getSurfaceControl());
+            }
+            return change;
         }
 
         @NonNull
@@ -435,8 +452,25 @@
                 : null;
     }
 
+    @VisibleForTesting
+    void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+        registerOrganizerInternal(organizer, false /* isSystemOrganizer */);
+    }
+
     @Override
-    public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+    public void registerOrganizer(
+            @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+        registerOrganizerInternal(
+                organizer,
+                Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer);
+    }
+
+    @VisibleForTesting
+    void registerOrganizerInternal(
+            @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+        if (isSystemOrganizer) {
+            enforceTaskPermission("registerSystemOrganizer()");
+        }
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -448,7 +482,7 @@
                         "Replacing existing organizer currently unsupported");
             }
             mTaskFragmentOrganizerState.put(organizer.asBinder(),
-                    new TaskFragmentOrganizerState(organizer, pid, uid));
+                    new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer));
             mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>());
         }
     }
@@ -711,6 +745,12 @@
         }
     }
 
+    boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+        final TaskFragmentOrganizerState state =
+                mTaskFragmentOrganizerState.get(token.asBinder());
+        return state != null && state.mIsSystemOrganizer;
+    }
+
     @Nullable
     private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
             @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3faf8b9..3db7765 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9646,15 +9646,4 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
-
-    /**
-     * Resets the spatial ordering of recents for testing purposes.
-     */
-    void resetFreezeRecentTaskListReordering() {
-        if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS,
-                "resetFreezeRecentTaskListReordering()")) {
-            throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
-        }
-        mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout();
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index fa9a65f..8fad950 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -142,8 +142,6 @@
                     return runReset(pw);
                 case "disable-blur":
                     return runSetBlurDisabled(pw);
-                case "reset-freeze-recent-tasks":
-                    return runResetFreezeRecentTaskListReordering(pw);
                 case "shell":
                     return runWmShellCommand(pw);
                 default:
@@ -254,11 +252,6 @@
         return 0;
     }
 
-    private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException {
-        mInternal.resetFreezeRecentTaskListReordering();
-        return 0;
-    }
-
     private void printInitialDisplayDensity(PrintWriter pw , int displayId) {
         try {
             final int initialDensity = mInterface.getInitialDisplayDensity(displayId);
@@ -1499,8 +1492,6 @@
         printLetterboxHelp(pw);
         printMultiWindowConfigHelp(pw);
 
-        pw.println("  reset-freeze-recent-tasks");
-        pw.println("    Resets the spatial ordering of the recent tasks list");
         pw.println("  reset [-d DISPLAY_ID]");
         pw.println("    Reset all override settings.");
         if (!IS_USER) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6d7e297..376cad7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -99,6 +99,7 @@
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -1993,8 +1994,10 @@
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
-        taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
-                ownerActivity.getUid(), ownerActivity.info.processName);
+        TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
+        taskFragment.setTaskFragmentOrganizer(organizerToken,
+                ownerActivity.getUid(), ownerActivity.info.processName,
+                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
             // When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING
index eb9db70..7f7eb48 100644
--- a/services/core/jni/TEST_MAPPING
+++ b/services/core/jni/TEST_MAPPING
@@ -7,7 +7,6 @@
       "name": "CtsVibratorTestCases",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index fccd1ec..0d5534b 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsDevicePolicyManagerTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
           "exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index cedbfd2b..de9f771 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "FrameworksInputMethodSystemServerTests",
       "options": [
         {"include-filter": "com.android.server.inputmethod"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
@@ -15,7 +14,6 @@
       "name": "FrameworksImeTests",
       "options": [
         {"include-filter": "com.android.inputmethodservice"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index dca69eb..8b54d6d2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.Sensor;
@@ -158,6 +159,7 @@
                     .setStrictness(Strictness.LENIENT)
                     .spyStatic(SystemProperties.class)
                     .spyStatic(BatteryStatsService.class)
+                    .spyStatic(ActivityManager.class)
                     .build();
 
     @Rule
@@ -182,9 +184,14 @@
 
         mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> false)
+                .when(ActivityManager::isLowRamDeviceStatic);
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index edaa1d5..37ee23f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.Sensor;
@@ -158,6 +159,7 @@
                     .setStrictness(Strictness.LENIENT)
                     .spyStatic(SystemProperties.class)
                     .spyStatic(BatteryStatsService.class)
+                    .spyStatic(ActivityManager.class)
                     .build();
 
     @Rule
@@ -186,9 +188,14 @@
 
         mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> false)
+                .when(ActivityManager::isLowRamDeviceStatic);
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c3322ec..c63fac9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -118,7 +118,8 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 4
+        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
+        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -146,7 +147,8 @@
         mClock.fastForward(1000);
         mTestHandler.timeAdvance();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(0.2f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 2
+        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
+        assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -173,7 +175,8 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
+        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING
index d73d83d..a644ea6 100644
--- a/services/tests/dreamservicetests/TEST_MAPPING
+++ b/services/tests/dreamservicetests/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "DreamServiceTests",
       "options": [
         {"include-filter": "com.android.server.dreams"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
index 3514276..4c4e9df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/FoldSettingProviderTest.java
@@ -27,13 +27,12 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.res.Resources;
 import android.os.UserHandle;
 import android.provider.Settings;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.internal.R;
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
 import com.android.internal.util.SettingsWrapper;
 
 import org.junit.Before;
@@ -48,9 +47,9 @@
     @Mock
     private Context mContext;
     @Mock
-    private Resources mResources;
-    @Mock
     private SettingsWrapper mSettingsWrapper;
+    @Mock
+    private FoldLockSettingAvailabilityProvider mFoldLockSettingAvailabilityProvider;
     private ContentResolver mContentResolver;
     private FoldSettingProvider mFoldSettingProvider;
 
@@ -61,17 +60,18 @@
         mContentResolver =
                 InstrumentationRegistry.getInstrumentation().getContext().getContentResolver();
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
-        when(mContext.getResources()).thenReturn(mResources);
         setFoldLockBehaviorAvailability(true);
 
-        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+                mFoldLockSettingAvailabilityProvider);
     }
 
     @Test
     public void foldSettingNotAvailable_returnDefaultSetting() {
         setFoldLockBehaviorAvailability(false);
         setFoldLockBehaviorSettingValue(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
-        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+                mFoldLockSettingAvailabilityProvider);
 
         boolean shouldSelectiveStayAwakeOnFold =
                 mFoldSettingProvider.shouldSelectiveStayAwakeOnFold();
@@ -83,7 +83,8 @@
     public void foldSettingNotAvailable_notReturnStayAwakeOnFoldTrue() {
         setFoldLockBehaviorAvailability(false);
         setFoldLockBehaviorSettingValue(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
-        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+                mFoldLockSettingAvailabilityProvider);
 
         boolean shouldStayAwakeOnFold = mFoldSettingProvider.shouldStayAwakeOnFold();
 
@@ -94,7 +95,8 @@
     public void foldSettingNotAvailable_notReturnSleepOnFoldTrue() {
         setFoldLockBehaviorAvailability(false);
         setFoldLockBehaviorSettingValue(SETTING_VALUE_SLEEP_ON_FOLD);
-        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper);
+        mFoldSettingProvider = new FoldSettingProvider(mContext, mSettingsWrapper,
+                mFoldLockSettingAvailabilityProvider);
 
         boolean shouldSleepOnFold = mFoldSettingProvider.shouldSleepOnFold();
 
@@ -131,7 +133,7 @@
     }
 
     private void setFoldLockBehaviorAvailability(boolean isFoldLockBehaviorEnabled) {
-        when(mResources.getBoolean(R.bool.config_fold_lock_behavior)).thenReturn(
+        when(mFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable()).thenReturn(
                 isFoldLockBehaviorEnabled);
     }
 
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index e1eb1e4..eee68a4 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "PowerStatsTests",
       "options": [
         {"include-filter": "com.android.server.power.stats"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index a508718..b69b554 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -125,6 +125,8 @@
                 eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
                 anyLong(), anyInt(), eq(requireConfirmation),
                 eq(targetUserId), any());
+
+        verify(mAuthenticationStatsCollector).authenticate(eq(targetUserId), eq(authenticated));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
new file mode 100644
index 0000000..8f77e9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.IVirtualCameraSession;
+import android.companion.virtual.camera.VirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.companion.virtual.camera.VirtualCameraSession;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtual.flags.Flags;
+import android.content.ComponentName;
+import android.graphics.ImageFormat;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.server.companion.virtual.camera.IVirtualCameraService;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class VirtualCameraTest {
+
+    private static final String PKG = "com.android.virtualcamera";
+    private static final String CLS = ".VirtualCameraService";
+    public static final String CAMERA_DISPLAY_NAME = "testCamera";
+
+    private final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+    private FakeVirtualCameraService mFakeVirtualCameraService;
+    private VirtualCameraController mVirtualCameraController;
+
+    @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext);
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule =
+            new AdoptShellPermissionsRule(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    Manifest.permission.CREATE_VIRTUAL_DEVICE);
+
+    @Before
+    public void setUp() {
+        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController);
+        mFakeVirtualCameraService = new FakeVirtualCameraService();
+        connectFakeService();
+        mVirtualCameraController = new VirtualCameraController(mContext);
+    }
+
+    private VirtualDeviceImpl createVirtualDevice() {
+        return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
+    }
+
+    private void connectFakeService() {
+        mContext.addMockService(
+                ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder());
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+    @Test
+    public void addVirtualCamera() {
+        VirtualDeviceImpl virtualDevice = createVirtualDevice();
+        VirtualCameraConfig config = createVirtualCameraConfig(null);
+        IVirtualCamera.Default camera = new IVirtualCamera.Default();
+        virtualDevice.registerVirtualCamera(camera);
+
+        assertThat(mFakeVirtualCameraService.mCameras).contains(camera);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+    @Test
+    public void addVirtualCamera_serviceNotReady() {
+        TestableContext context =
+                new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+        VirtualCameraController virtualCameraController = new VirtualCameraController(context);
+        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController);
+
+        VirtualDeviceImpl virtualDevice =
+                mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
+        IVirtualCamera.Default camera = new IVirtualCamera.Default();
+        VirtualCameraConfig config = createVirtualCameraConfig(null);
+        virtualDevice.registerVirtualCamera(camera);
+        FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService();
+
+        // Only add the service after connecting the camera
+        virtualCameraController.onServiceConnected(
+                ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder());
+
+        assertThat(fakeVirtualCameraService.mCameras).contains(camera);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+    @Test
+    public void getCameraConfiguration() {
+        VirtualDeviceImpl virtualDevice = createVirtualDevice();
+        VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
+        VirtualCameraConfig config =
+                new VirtualCameraConfig.Builder()
+                        .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
+                        .setDisplayName(CAMERA_DISPLAY_NAME)
+                        .setCallback(
+                                new HandlerExecutor(new Handler(Looper.getMainLooper())),
+                                () -> virtualCameraSession)
+                        .build();
+
+        VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
+
+        VirtualCameraConfig returnedConfig = virtualCamera.getConfig();
+        assertThat(returnedConfig).isNotNull();
+        assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME);
+        Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs();
+        assertThat(streamConfigs).hasSize(1);
+        VirtualCameraStreamConfig streamConfig =
+                streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0];
+        assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565);
+        assertThat(streamConfig.width).isEqualTo(10);
+        assertThat(streamConfig.height).isEqualTo(10);
+
+        VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig();
+        assertThat(halConfig).isNotNull();
+        assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME);
+        assertThat(halConfig.streamConfigs).asList().hasSize(1);
+        assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565);
+        assertThat(halConfig.streamConfigs[0].width).isEqualTo(10);
+        assertThat(halConfig.streamConfigs[0].height).isEqualTo(10);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
+    @Test
+    public void createCameraWithVirtualCameraInstance() {
+        VirtualDeviceImpl virtualDevice = createVirtualDevice();
+
+        VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
+        VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession);
+        VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
+
+        assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera);
+        assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class);
+    }
+
+    @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA)
+    @Test
+    public void createCameraDoesNothingWhenControllerIsNull() {
+        mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null);
+        VirtualDeviceImpl virtualDevice = createVirtualDevice();
+        IVirtualCamera.Default camera = new IVirtualCamera.Default();
+        VirtualCameraConfig config = createVirtualCameraConfig(null);
+        virtualDevice.registerVirtualCamera(camera);
+
+        assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera);
+    }
+
+    @NonNull
+    private static VirtualCameraConfig createVirtualCameraConfig(
+            VirtualCameraSession virtualCameraSession) {
+        return new VirtualCameraConfig.Builder()
+                .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
+                .setDisplayName(CAMERA_DISPLAY_NAME)
+                .setCallback(
+                        new HandlerExecutor(new Handler(Looper.getMainLooper())),
+                        () -> virtualCameraSession)
+                .build();
+    }
+
+    private static class FakeVirtualCameraService extends IVirtualCameraService.Stub {
+
+        final Set<IVirtualCamera> mCameras = new HashSet<>();
+
+        @Override
+        public boolean registerCamera(IVirtualCamera camera) throws RemoteException {
+            mCameras.add(camera);
+            return true;
+        }
+
+        @Override
+        public void unregisterCamera(IVirtualCamera camera) throws RemoteException {
+            mCameras.remove(camera);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 263d470..61b30a0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -118,6 +118,7 @@
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 
@@ -1841,13 +1842,25 @@
 
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
             VirtualDeviceParams params) {
-        VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, mVdms, mVirtualDeviceLog, new Binder(),
-                new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
-                virtualDeviceId,
-                mInputController, mCameraAccessController,
-                mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
-                mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
+        VirtualDeviceImpl virtualDeviceImpl =
+                new VirtualDeviceImpl(
+                        mContext,
+                        mAssociationInfo,
+                        mVdms,
+                        mVirtualDeviceLog,
+                        new Binder(),
+                        new AttributionSource(
+                                ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
+                        virtualDeviceId,
+                        mInputController,
+                        mCameraAccessController,
+                        mPendingTrampolineCallback,
+                        mActivityListener,
+                        mSoundEffectListener,
+                        mRunningAppsChangedCallback,
+                        params,
+                        new DisplayManagerGlobal(mIDisplayManager),
+                        new VirtualCameraController(mContext));
         mVdms.addVirtualDevice(virtualDeviceImpl);
         assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
         assertThat(virtualDeviceImpl.getPersistentDeviceId())
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
new file mode 100644
index 0000000..af633cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import android.app.admin.DevicePolicyManager;
+import android.companion.AssociationInfo;
+import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.flags.Flags;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.net.MacAddress;
+import android.os.Binder;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.camera.VirtualCameraController;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/** Test rule to generate instances of {@link VirtualDeviceImpl}. */
+public class VirtualDeviceRule implements TestRule {
+
+    private static final int DEVICE_OWNER_UID = 50;
+    private static final int VIRTUAL_DEVICE_ID = 42;
+
+    private final Context mContext;
+    private InputController mInputController;
+    private CameraAccessController mCameraAccessController;
+    private AssociationInfo mAssociationInfo;
+    private VirtualDeviceManagerService mVdms;
+    private VirtualDeviceManagerInternal mLocalService;
+    private VirtualDeviceLog mVirtualDeviceLog;
+
+    // Mocks
+    @Mock private InputController.NativeWrapper mNativeWrapperMock;
+    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock private IDisplayManager mIDisplayManager;
+    @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
+    @Mock private DevicePolicyManager mDevicePolicyManagerMock;
+    @Mock private InputManagerInternal mInputManagerInternalMock;
+    @Mock private SensorManagerInternal mSensorManagerInternalMock;
+    @Mock private IVirtualDeviceActivityListener mActivityListener;
+    @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener;
+    @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+    @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
+
+    // Test instance suppliers
+    private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier;
+
+    /**
+     * Create a new {@link VirtualDeviceRule}
+     *
+     * @param context The context to be used with the rule.
+     */
+    public VirtualDeviceRule(@NonNull Context context) {
+        Objects.requireNonNull(context);
+        mContext = context;
+    }
+
+    /**
+     * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the
+     * supplier returns null, a new instance will be created.
+     */
+    public VirtualDeviceRule withVirtualCameraControllerSupplier(
+            Supplier<VirtualCameraController> virtualCameraControllerSupplier) {
+        mVirtualCameraControllerSupplier = virtualCameraControllerSupplier;
+        return this;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                init(new TestableContext(mContext));
+                base.evaluate();
+            }
+        };
+    }
+
+    private void init(@NonNull TestableContext context) {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+        doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
+        doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.uniqueId = "uniqueId";
+        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock);
+
+        // Allow virtual devices to be created on the looper thread for testing.
+        final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+        mInputController =
+                new InputController(
+                        mNativeWrapperMock,
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getMainThreadHandler(),
+                        context.getSystemService(WindowManager.class),
+                        threadVerifier);
+        mCameraAccessController =
+                new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback);
+
+        mAssociationInfo =
+                new AssociationInfo(
+                        /* associationId= */ 1,
+                        0,
+                        null,
+                        null,
+                        MacAddress.BROADCAST_ADDRESS,
+                        "",
+                        null,
+                        null,
+                        true,
+                        false,
+                        false,
+                        0,
+                        0,
+                        -1);
+
+        mVdms = new VirtualDeviceManagerService(context);
+        mLocalService = mVdms.getLocalServiceInstance();
+        mVirtualDeviceLog = new VirtualDeviceLog(context);
+    }
+
+    /**
+     * Create a {@link VirtualDeviceImpl} with the required mocks
+     *
+     * @param params See {@link
+     *     android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int,
+     *     VirtualDeviceParams)}
+     */
+    public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) {
+        VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get();
+        if (Flags.virtualCamera()) {
+            if (virtualCameraController == null) {
+                virtualCameraController = new VirtualCameraController(mContext);
+            }
+        }
+
+        VirtualDeviceImpl virtualDeviceImpl =
+                new VirtualDeviceImpl(
+                        mContext,
+                        mAssociationInfo,
+                        mVdms,
+                        mVirtualDeviceLog,
+                        new Binder(),
+                        new AttributionSource(
+                                DEVICE_OWNER_UID,
+                                "com.android.virtualdevice.test",
+                                "virtualdevicerule"),
+                        VIRTUAL_DEVICE_ID,
+                        mInputController,
+                        mCameraAccessController,
+                        mPendingTrampolineCallback,
+                        mActivityListener,
+                        mSoundEffectListener,
+                        mRunningAppsChangedCallback,
+                        params,
+                        new DisplayManagerGlobal(mIDisplayManager),
+                        virtualCameraController);
+        mVdms.addVirtualDevice(virtualDeviceImpl);
+        return virtualDeviceImpl;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 4d1e405..e9d8b2e 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -7,7 +7,7 @@
                     "include-filter": "com.android.server.recoverysystem."
                 },
                 {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
                 }
             ]
         }
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 5a482fc..f221b75 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -484,6 +484,7 @@
                 new Callback());
 
         verify(mMockStatusBar).requestAddTile(
+                eq(Binder.getCallingUid()),
                 eq(TEST_COMPONENT),
                 eq(APP_NAME),
                 eq(TILE_LABEL),
@@ -534,6 +535,7 @@
         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
 
         verify(mMockStatusBar).requestAddTile(
+                eq(Binder.getCallingUid()),
                 eq(TEST_COMPONENT),
                 eq(APP_NAME),
                 eq(TILE_LABEL),
@@ -555,6 +557,7 @@
         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
 
         verify(mMockStatusBar).requestAddTile(
+                eq(Binder.getCallingUid()),
                 eq(TEST_COMPONENT),
                 eq(APP_NAME),
                 eq(TILE_LABEL),
@@ -577,6 +580,7 @@
         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
 
         verify(mMockStatusBar).requestAddTile(
+                eq(Binder.getCallingUid()),
                 eq(TEST_COMPONENT),
                 eq(APP_NAME),
                 eq(TILE_LABEL),
@@ -599,6 +603,7 @@
         mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
 
         verify(mMockStatusBar).requestAddTile(
+                eq(Binder.getCallingUid()),
                 eq(TEST_COMPONENT),
                 eq(APP_NAME),
                 eq(TILE_LABEL),
@@ -623,6 +628,7 @@
                     new Callback());
 
             verify(mMockStatusBar, times(i + 1)).requestAddTile(
+                    eq(Binder.getCallingUid()),
                     eq(TEST_COMPONENT),
                     eq(APP_NAME),
                     eq(TILE_LABEL),
@@ -638,6 +644,7 @@
 
         // Only called MAX_NUM_DENIALS times
         verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+                anyInt(),
                 any(),
                 any(),
                 any(),
@@ -658,6 +665,7 @@
                     new Callback());
 
             verify(mMockStatusBar, times(i + 1)).requestAddTile(
+                    eq(Binder.getCallingUid()),
                     eq(TEST_COMPONENT),
                     eq(APP_NAME),
                     eq(TILE_LABEL),
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index bfa279d..2bf1385 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -212,7 +212,30 @@
         mController.dispatchPendingEvents();
 
         assertTaskFragmentParentInfoChangedTransaction(mTask);
-        assertTaskFragmentAppearedTransaction();
+        assertTaskFragmentAppearedTransaction(false /* hasSurfaceControl */);
+    }
+
+    @Test
+    public void testOnTaskFragmentAppeared_systemOrganizer() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+        // No-op when the TaskFragment is not attached.
+        mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+        mController.dispatchPendingEvents();
+
+        verify(mOrganizer, never()).onTransactionReady(any());
+
+        // Send callback when the TaskFragment is attached.
+        setupMockParent(mTaskFragment, mTask);
+
+        mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+        mController.dispatchPendingEvents();
+
+        assertTaskFragmentParentInfoChangedTransaction(mTask);
+
+        // System organizer should receive the SurfaceControl
+        assertTaskFragmentAppearedTransaction(true /* hasSurfaceControl */);
     }
 
     @Test
@@ -1664,7 +1687,7 @@
     }
 
     /** Asserts that there will be a transaction for TaskFragment appeared. */
-    private void assertTaskFragmentAppearedTransaction() {
+    private void assertTaskFragmentAppearedTransaction(boolean hasSurfaceControl) {
         verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
         final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
         final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
@@ -1675,6 +1698,11 @@
         assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
         assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
         assertEquals(mFragmentToken, change.getTaskFragmentToken());
+        if (hasSurfaceControl) {
+            assertNotNull(change.getTaskFragmentSurfaceControl());
+        } else {
+            assertNull(change.getTaskFragmentSurfaceControl());
+        }
     }
 
     /** Asserts that there will be a transaction for TaskFragment info changed. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6a9bb6c..5205bb0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -27,8 +27,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -124,6 +128,17 @@
     }
 
     @Test
+    public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() {
+        clearInvocations(mTransaction);
+        mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true;
+
+        mTaskFragment.updateOrganizedTaskFragmentSurface();
+
+        verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat());
+        verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt());
+    }
+
+    @Test
     public void testShouldStartChangeTransition_relativePositionChange() {
         final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
                 ACTIVITY_TYPE_STANDARD);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 98cc1da..138e575 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2403,10 +2403,6 @@
             }
 
             @Override
-            public void onHandleUserStop(Intent intent, int userHandle) {
-            }
-
-            @Override
             public void onPackageModified(@NonNull String pkgName) {
                 // If the package modified is not in the current user, then don't bother making
                 // any changes as we are going to do any initialization needed when we switch users.
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 29a952a..250c3a5 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -35,6 +35,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -46,7 +48,8 @@
     private static final String LOG_TAG = "TelephonyPermissions";
 
     private static final boolean DBG = false;
-
+    /** Feature flags */
+    private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl();
     /**
      * Whether to disable the new device identifier access restrictions.
      */
@@ -854,7 +857,8 @@
     public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
             @NonNull UserHandle callerUserHandle, @NonNull String destAddr) {
         // Skip subscription-user association check for emergency numbers
-        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        TelephonyManager tm = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
         final long token = Binder.clearCallingIdentity();
         try {
             if (tm != null && tm.isEmergencyNumber(destAddr)) {
@@ -876,16 +880,19 @@
      * @param context Context
      * @param subId subscription ID
      * @param callerUserHandle caller user handle
-     * @return  false if user is not associated with the subscription.
+     * @return  false if user is not associated with the subscription, or no record found of this
+     * subscription.
      */
     public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
             @NonNull UserHandle callerUserHandle) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            // No subscription on device, return true.
+        if (!sFeatureFlag.rejectBadSubIdInteraction()
+                && !SubscriptionManager.isValidSubscriptionId(subId)) {
+            // Return true for invalid sub Id.
             return true;
         }
 
-        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+        SubscriptionManager subManager = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         final long token = Binder.clearCallingIdentity();
         try {
             if ((subManager != null) &&
@@ -894,8 +901,11 @@
                 Log.e(LOG_TAG, "User[User ID:" + callerUserHandle.getIdentifier()
                         + "] is not associated with Subscription ID:" + subId);
                 return false;
-
             }
+        } catch (IllegalArgumentException e) {
+            // Found no record of this sub Id.
+            Log.e(LOG_TAG, "Subscription[Subscription ID:" + subId + "] has no records on device");
+            return !sFeatureFlag.rejectBadSubIdInteraction();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 9a8c965..aed8fb8 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -274,6 +274,10 @@
 
             SubscriptionManager subscriptionManager = context.getSystemService(
                     SubscriptionManager.class);
+            if (!subscriptionManager.isActiveSubscriptionId(subId)) {
+                Log.e(LOG_TAG, "Tried to send message with an inactive subscription " + subId);
+                return;
+            }
             UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
             UserManager um = context.getSystemService(UserManager.class);
 
@@ -319,4 +323,4 @@
         return false;
 
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 36a8fc1..a5e0638 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,6 +53,7 @@
 import android.telephony.ims.feature.RcsFeature;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.util.List;
@@ -9436,22 +9438,9 @@
      * </carrier_config>
      * }</pre>
      * <p>
-     * If this carrier config is not present, the device overlay config
-     * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
-     * is present, the supported services associated with the PLMNs listed in the carrier config
-     * will override that of the device overlay config. The supported satellite services will be
-     * identified as follows:
-     * <ul>
-     * <li>For each PLMN that exists only in the carrier provided satellite services, use the
-     * carrier provided services as the supported services.</li>
-     * <li>For each PLMN that is present only in the device provided satellite services, use the
-     * device provided services as the supported services.</li>
-     * <li>For each PLMN that is present in both the carrier provided and device provided satellite
-     * services, use the carrier provided services as the supported services.</li>
-     * </ul>
-     * <p>
      * This config is empty by default.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
             "carrier_supported_satellite_services_per_provider_bundle";
 
@@ -9462,9 +9451,8 @@
      * satellite provider and the carrier before enabling this flag.
      *
      * The default value is false.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
             "satellite_attach_supported_bool";
 
@@ -10430,8 +10418,12 @@
         sDefaults.putAll(Iwlan.getDefaults());
         sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
-        sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
-                new int[] {4 /* BUSY */});
+        if (Flags.doNotOverridePreciseLabel()) {
+            sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[]{});
+        } else {
+            sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+                    new int[]{4 /* BUSY */});
+        }
         sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
         sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000);
         sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 29149b9..fa5fd87 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4348,7 +4348,7 @@
      * {code true} if there are no subscriptions on device
      * else {@code false} if subscription is not associated with user.
      *
-     * @throws IllegalArgumentException if subscription is invalid.
+     * @throws IllegalArgumentException if subscription doesn't exist.
      * @throws SecurityException if the caller doesn't have permissions required.
      *
      * @hide
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index a4527f12..8dc2de8 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -85,29 +85,11 @@
     @Nullable private final Context mContext;
 
     /**
-     * Create a new SatelliteManager object pinned to the given subscription ID.
-     * This is needed only to handle carrier specific satellite features.
-     * For eg: requestSatelliteAttachEnabledForCarrier and
-     *         requestIsSatelliteAttachEnabledForCarrier
-     *
-     * @return a SatelliteManager that uses the given subId for all satellite activities.
-     * @throws IllegalArgumentException if the subscription is invalid.
-     * @hide
-     */
-    public SatelliteManager createForSubscriptionId(int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            throw new IllegalArgumentException("Invalid subscription ID");
-        }
-        return new SatelliteManager(mContext, subId);
-    }
-
-    /**
      * Create an instance of the SatelliteManager.
      *
      * @param context The context the SatelliteManager belongs to.
      * @hide
      */
-
     public SatelliteManager(@Nullable Context context) {
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     }
@@ -846,8 +828,8 @@
     /**
      * Satellite communication restricted by geolocation. This can be
      * triggered based upon geofence input provided by carrier to enable or disable satellite.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
 
     /** @hide */
@@ -1643,26 +1625,28 @@
      * {@code true}.</li>
      * </ul>
      *
+     * @param subId The subscription ID of the carrier.
      * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
         if (enableSatellite) {
-            removeSatelliteAttachRestrictionForCarrier(
+            removeSatelliteAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         } else {
-            addSatelliteAttachRestrictionForCarrier(
+            addSatelliteAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         }
     }
@@ -1671,6 +1655,7 @@
      * Request to get whether the carrier supported satellite plmn scan and attach by modem is
      * enabled by user.
      *
+     * @param subId The subscription ID of the carrier.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -1681,16 +1666,17 @@
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestIsSatelliteAttachEnabledForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void requestIsSatelliteAttachEnabledForCarrier(int subId,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
-        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId);
         executor.execute(() -> callback.onResult(
                 !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
     }
@@ -1699,19 +1685,25 @@
      * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
      * by modem.
      *
+     * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void addSatelliteAttachRestrictionForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void addSatelliteAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -1722,7 +1714,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+                telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1736,19 +1728,25 @@
      * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
      * by modem.
      *
+     * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void removeSatelliteAttachRestrictionForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void removeSatelliteAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -1759,7 +1757,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+                telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1773,34 +1771,40 @@
      * Get reasons for disallowing satellite attach, as requested by
      * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
      *
+     * @param subId The subscription ID of the carrier.
      * @return Set of reasons for disallowing satellite communication.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteCommunicationRestrictionReason
-    public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 int[] receivedArray =
-                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId);
                 if (receivedArray.length == 0) {
-                    logd("received set is empty, create empty set");
+                    logd("receivedArray is empty, create empty set");
                     return new HashSet<>();
                 } else {
                     return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
                 }
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                throw new IllegalStateException("Telephony service is null.");
             }
         } catch (RemoteException ex) {
             loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
-        return null;
+        return new HashSet<>();
     }
 
     private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 02661de..0fcd0d6 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -392,7 +392,11 @@
      *
      * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
      *                this information to determine the relevant carrier.
-     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -404,8 +408,8 @@
      *   SatelliteError:RADIO_NOT_AVAILABLE
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      */
-    void setSatellitePlmn(int simSlot, in List<String> plmnList,
-            in IIntegerConsumer resultCallback);
+    void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
+            in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
 
     /**
      * Enable or disable satellite in the cellular modem associated with a carrier.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 6451daf..a9c09c9 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -214,12 +214,12 @@
         }
 
         @Override
-        public void setSatellitePlmn(int simSlot, List<String> plmnList,
-                IIntegerConsumer errorCallback)
+        public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
+                List<String> devicePlmnList, IIntegerConsumer errorCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .setSatellitePlmn(simSlot, plmnList, errorCallback),
+                    () -> SatelliteImplBase.this.setSatellitePlmn(
+                            simSlot, carrierPlmnList, devicePlmnList, errorCallback),
                     "setSatellitePlmn");
         }
 
@@ -655,13 +655,15 @@
      * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
      * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
      * satellite LTE service.
-     * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
-     * and return an error otherwise.
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
      * @param errorCallback The callback to receive the error code result of the operation.
-     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
      *
      * Valid error codes returned:
      *   SatelliteError:NONE
@@ -672,7 +674,8 @@
      *   SatelliteError:RADIO_NOT_AVAILABLE
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      */
-    public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+    public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
+            @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
diff --git a/tests/utils/testutils/TEST_MAPPING b/tests/utils/testutils/TEST_MAPPING
index 6468d8c..52fd5a8 100644
--- a/tests/utils/testutils/TEST_MAPPING
+++ b/tests/utils/testutils/TEST_MAPPING
@@ -7,9 +7,6 @@
           "exclude-annotation": "androidx.test.filters.LargeTest"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 95cbff9..d176b5e 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -69,7 +69,8 @@
             String methodDescriptor,
             String callbackMethod
     ) {
-        callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor);
+        callStaticMethodByName(callbackMethod, "method call hook", methodClass,
+                methodName, methodDescriptor);
     }
 
     /**
@@ -167,31 +168,38 @@
         logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
                 + " calling hook " + callbackMethod);
 
-        callStaticMethodByName(callbackMethod, loadedClass);
+        callStaticMethodByName(callbackMethod, "class load hook", loadedClass);
     }
 
-    private static void callStaticMethodByName(String classAndMethodName, Object... args) {
+    private static void callStaticMethodByName(String classAndMethodName,
+            String description, Object... args) {
         // Forward the call to callbackMethod.
         final int lastPeriod = classAndMethodName.lastIndexOf(".");
-        final String className = classAndMethodName.substring(0, lastPeriod);
-        final String methodName = classAndMethodName.substring(lastPeriod + 1);
 
-        if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
+        if ((lastPeriod) < 0 || (lastPeriod == classAndMethodName.length() - 1)) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: malformed method name \"%s\"",
+                    "Unable to find %s: malformed method name \"%s\"",
+                    description,
                     classAndMethodName));
         }
 
+        final String className = classAndMethodName.substring(0, lastPeriod);
+        final String methodName = classAndMethodName.substring(lastPeriod + 1);
+
         Class<?> clazz = null;
         try {
             clazz = Class.forName(className);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Class %s not found", className), e);
+                    "Unable to find %s: Class %s not found",
+                    description,
+                    className), e);
         }
         if (!Modifier.isPublic(clazz.getModifiers())) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Class %s must be public", className));
+                    "Unable to find %s: Class %s must be public",
+                    description,
+                    className));
         }
 
         Class<?>[] argTypes = new Class[args.length];
@@ -204,25 +212,23 @@
             method = clazz.getMethod(methodName, argTypes);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: class %s doesn't have method %s"
+                    "Unable to find %s: class %s doesn't have method %s"
                             + " (method must take exactly one parameter of type Class,"
                             + " and public static)",
-                    className,
-                    methodName), e);
+                    description, className, methodName), e);
         }
         if (!(Modifier.isPublic(method.getModifiers())
                 && Modifier.isStatic(method.getModifiers()))) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Method %s in class %s must be public static",
-                    methodName, className));
+                    "Unable to find %s: Method %s in class %s must be public static",
+                    description, methodName, className));
         }
         try {
             method.invoke(null, args);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to invoke class load hook %s.%s",
-                    className,
-                    methodName), e);
+                    "Unable to invoke %s %s.%s",
+                    description, className, methodName), e);
         }
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 7d7852a..f75062b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -32,6 +32,10 @@
     return uncommented.trim()
 }
 
+/**
+ * Concatenate list [a] and [b] and return it. As an optimization, it returns an input
+ * [List] as-is if the other [List] is empty, so do not modify input [List]'s.
+ */
 fun <T> addLists(a: List<T>, b: List<T>): List<T> {
     if (a.isEmpty()) {
         return b
@@ -42,6 +46,10 @@
     return a + b
 }
 
+/**
+ * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a].
+ * (because the method may return [a] as-is, do not modify it after passing it.)
+ */
 fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
     if (b == null) {
         return a