Merge "From dream, FP auth brings you to the last app" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b1f587e..ab20edc 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -38,6 +38,7 @@
     ":display_flags_lib{.generated_srcjars}",
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.app.flags-aconfig-java{.generated_srcjars}",
+    ":android.credentials.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -355,3 +356,16 @@
     aconfig_declarations: "android.hardware.radio.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Credential Manager
+aconfig_declarations {
+    name: "android.credentials.flags-aconfig",
+    package: "android.credentials.flags",
+    srcs: ["core/java/android/credentials/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.credentials.flags-aconfig-java",
+    aconfig_declarations: "android.credentials.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index fce5e4f..e7a5b72 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1759,12 +1759,17 @@
 
     /**
      * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
-     * returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * the caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
-     * In order to use this, apps should declare a {@code <queries>} tag with the action
-     * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+     * Before Android U, this method requires the
+     * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+     * </p>
+     *
+     * <p>
+     * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
+     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
      * this method will return {@code null} if the caller doesn't otherwise have
      * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
      * </p>
@@ -1780,8 +1785,15 @@
 
     /**
      * Returns the information about the designated wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
-     * returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * the caller doesn't have the appropriate permissions, this returns {@code null}.
+     *
+     * <p>
+     * In order to use this, apps should declare a {@code <queries>} tag
+     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+     * this method will return {@code null} if the caller doesn't otherwise have
+     * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
+     * </p>
      *
      * @param which Specifies wallpaper to request (home or lock).
      * @param userId Owner of the wallpaper.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b5efe58..b054d38 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -27,3 +27,11 @@
     description: "Feature flag to improve stopped state enforcement"
     bug: "296644915"
 }
+
+flag {
+    name: "nullable_data_dir"
+    namespace: "package_manager_service"
+    description: "Feature flag to allow ApplicationInfo.dataDir to be null."
+    bug: "302587814"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
new file mode 100644
index 0000000..0d0305b
--- /dev/null
+++ b/core/java/android/credentials/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.credentials.flags"
+
+flag {
+    namespace: "credential_manager"
+    name: "settings_activity_enabled"
+    description: "Enable the Credential Manager Settings Activity APIs"
+    bug: "300014059"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f40232b..d09f0a8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11147,12 +11147,6 @@
         public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
 
         /**
-         * Volume dialog timeout in ms.
-         * @hide
-         */
-        public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout";
-
-        /**
          * What behavior should be invoked when the volume hush gesture is triggered
          * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE.
          *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7674131..6b0dacc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32649,7 +32649,7 @@
 
     /**
      * Return whether the View allows automatic handwriting initiation. Returns true if automatic
-     * handwriting initiation is enabled, and verse visa.
+     * handwriting initiation is enabled, and vice versa.
      * @see #setAutoHandwritingEnabled(boolean)
      */
     public boolean isAutoHandwritingEnabled() {
diff --git a/core/java/android/webkit/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING
index c1bc6d7..07f4383 100644
--- a/core/java/android/webkit/TEST_MAPPING
+++ b/core/java/android/webkit/TEST_MAPPING
@@ -26,6 +26,7 @@
     },
     {
       "name": "GtsWebViewTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "exclude-annotation": "android.test.FlakyTest"
@@ -34,6 +35,7 @@
     },
     {
       "name": "GtsWebViewHostTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "exclude-annotation": "android.test.FlakyTest"
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 6a1f3f9..ade20d2 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -60,6 +60,12 @@
     src: "preinstalled-packages-asl-files.xml",
 }
 
+prebuilt_etc {
+    name: "preinstalled-packages-strict-signature.xml",
+    sub_dir: "sysconfig",
+    src: "preinstalled-packages-strict-signature.xml",
+}
+
 // Privapp permission whitelist files
 
 prebuilt_etc {
diff --git a/data/etc/preinstalled-packages-strict-signature.xml b/data/etc/preinstalled-packages-strict-signature.xml
new file mode 100644
index 0000000..3cbfa8c
--- /dev/null
+++ b/data/etc/preinstalled-packages-strict-signature.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!--
+This XML file declares which preinstalled apps, after updated, need to have strict signature check
+in boot time and avoid the cached results. This is to ensure the updated version still verifies
+against the preinstalled version.
+
+Example usage:
+    <require-strict-signature package="com.foo.bar"/>
+-->
+
+<config></config>
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 31092536..51b720d 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -430,19 +430,17 @@
 key 659   MACRO_4
 
 # Keys defined by HID usages
-key usage 0x0c0067 WINDOW
-key usage 0x0c006F BRIGHTNESS_UP
-key usage 0x0c0070 BRIGHTNESS_DOWN
-key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP
-key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN
-key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE
-key usage 0x0c0173 MEDIA_AUDIO_TRACK
-key usage 0x0c019C PROFILE_SWITCH
-key usage 0x0c01A2 ALL_APPS
-# TODO(b/297094448): Add stylus button mappings as a fallback when we have a way to determine
-#   if a device can actually report it.
-# key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
-# key usage 0x0d005a STYLUS_BUTTON_SECONDARY
+key usage 0x0c0067 WINDOW                    FALLBACK_USAGE_MAPPING
+key usage 0x0c006F BRIGHTNESS_UP             FALLBACK_USAGE_MAPPING
+key usage 0x0c0070 BRIGHTNESS_DOWN           FALLBACK_USAGE_MAPPING
+key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP     FALLBACK_USAGE_MAPPING
+key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN   FALLBACK_USAGE_MAPPING
+key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c0173 MEDIA_AUDIO_TRACK         FALLBACK_USAGE_MAPPING
+key usage 0x0c019C PROFILE_SWITCH            FALLBACK_USAGE_MAPPING
+key usage 0x0c01A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
+key usage 0x0d0044 STYLUS_BUTTON_PRIMARY     FALLBACK_USAGE_MAPPING
+key usage 0x0d005a STYLUS_BUTTON_SECONDARY   FALLBACK_USAGE_MAPPING
 
 # Joystick and game controller axes.
 # Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index d9e38e2..ae0c2ab 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -451,6 +451,14 @@
 int defaultProtocol = info.getDefaultProtocol();
 </pre>
 
+<p>To register for callbacks when MIDI 2.0 devices are added or removed, use
+MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS as the transport.</p>
+
+<pre class=prettyprint>
+midiManager.registerDeviceCallback(
+        MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS, executor, callback);
+</pre>
+
 <h1 id=creating_a_midi_2_0_virtual_device_service>Creating a MIDI 2.0 Virtual Device Service</h1>
 
 
diff --git a/native/webview/TEST_MAPPING b/native/webview/TEST_MAPPING
index c1bc6d7..07f4383 100644
--- a/native/webview/TEST_MAPPING
+++ b/native/webview/TEST_MAPPING
@@ -26,6 +26,7 @@
     },
     {
       "name": "GtsWebViewTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "exclude-annotation": "android.test.FlakyTest"
@@ -34,6 +35,7 @@
     },
     {
       "name": "GtsWebViewHostTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "exclude-annotation": "android.test.FlakyTest"
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f6f75de..91d2d1b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -143,7 +143,6 @@
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
         Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
-        Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 8d13f01..bec1447 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -212,7 +212,6 @@
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
                 Secure.ENABLED_NOTIFICATION_LISTENERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5646abe..12a0770 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -18,6 +18,7 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
@@ -208,7 +209,7 @@
     public void setLockscreenClockY(int clockY) {
         if (mView.screenOffsetYPadding != clockY) {
             mView.screenOffsetYPadding = clockY;
-            mView.updateClockTargetRegions();
+            mView.post(() -> mView.updateClockTargetRegions());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f7a4d47..a8bf229 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -97,9 +97,9 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
@@ -1191,6 +1191,7 @@
                 mPopup.setOnItemClickListener((parent, view, pos, id) -> {
                     if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
                     if (!view.isEnabled()) return;
+                    if (mPopup == null) return;
                     // Subtract one for the header
                     UserRecord user = adapter.getItem(pos - 1);
                     if (user.isManageUsers || user.isAddSupervisedUser) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 51308b8..3f5ec7d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3297,29 +3297,27 @@
         if (unlockPossible) {
             mFingerprintCancelSignal = new CancellationSignal();
 
+            final FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+                    new FingerprintAuthenticateOptions.Builder()
+                        .setUserId(userId)
+                        .build();
+            if (mFingerprintInteractiveToAuthProvider != null) {
+                fingerprintAuthenticateOptions.setVendorReason(
+                        mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
+            }
+
             if (!isUnlockingWithFingerprintAllowed()) {
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
                         mFingerprintDetectionCallback,
-                        new FingerprintAuthenticateOptions.Builder()
-                                .setUserId(userId)
-                                .setVendorReason(
-                                        mFingerprintInteractiveToAuthProvider.getVendorExtension(
-                                                getCurrentUser()))
-                                .build());
+                        fingerprintAuthenticateOptions);
             } else {
                 mLogger.v("startListeningForFingerprint");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback,
                         null /* handler */,
-                        new FingerprintAuthenticateOptions.Builder()
-                                .setUserId(userId)
-                                .setVendorReason(
-                                        mFingerprintInteractiveToAuthProvider.getVendorExtension(
-                                                getCurrentUser()))
-                                .build()
-                );
+                        fingerprintAuthenticateOptions);
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a00c3b5..c79952e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -115,7 +115,7 @@
     // TODO(b/292213543): Tracking Bug
     @JvmField
     val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            unreleasedFlag("notification_group_expansion_change")
+            unreleasedFlag("notification_group_expansion_change", teamfood = true)
 
     // TODO(b/301955929)
     @JvmField
@@ -805,7 +805,7 @@
 
     /** Enable haptic slider component in the brightness slider */
     @JvmField
-    val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider")
+    val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider", teamfood = true)
 
     // TODO(b/287205379): Tracking bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 70df09d..69fe46a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -83,7 +83,7 @@
  */
 public abstract class QSTileImpl<TState extends State> implements QSTile, LifecycleOwner, Dumpable {
     protected final String TAG = "Tile." + getClass().getSimpleName();
-    protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
+    protected final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
 
     private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
     protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index fc9ecb3..9cf9714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -42,13 +42,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -67,6 +67,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder;
+import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.settings.SecureSettings;
@@ -110,6 +112,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final KeyguardStatusBarViewModel mKeyguardStatusBarViewModel;
     private final BiometricUnlockController mBiometricUnlockController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final StatusBarContentInsetsProvider mInsetsProvider;
@@ -283,6 +286,7 @@
             KeyguardStateController keyguardStateController,
             KeyguardBypassController bypassController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardStatusBarViewModel keyguardStatusBarViewModel,
             BiometricUnlockController biometricUnlockController,
             SysuiStatusBarStateController statusBarStateController,
             StatusBarContentInsetsProvider statusBarContentInsetsProvider,
@@ -309,6 +313,7 @@
         mKeyguardStateController = keyguardStateController;
         mKeyguardBypassController = bypassController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStatusBarViewModel = keyguardStatusBarViewModel;
         mBiometricUnlockController = biometricUnlockController;
         mStatusBarStateController = statusBarStateController;
         mInsetsProvider = statusBarContentInsetsProvider;
@@ -356,6 +361,9 @@
         super.onInit();
         mCarrierTextController.init();
         mBatteryMeterViewController.init();
+        if (isMigrationEnabled()) {
+            KeyguardStatusBarViewBinder.bind(mView, mKeyguardStatusBarViewModel);
+        }
     }
 
     @Override
@@ -380,7 +388,7 @@
             // whenever the shade is opened on top of lockscreen, and then re-attached when the
             // shade is closed. So, we need to re-add the IconManager each time we're re-attached to
             // get icon updates.
-            if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+            if (isMigrationEnabled()) {
                 mStatusBarIconController.addIconGroup(mTintedIconManager);
             }
         }
@@ -455,6 +463,10 @@
 
     /** Sets the dozing state. */
     public void setDozing(boolean dozing) {
+        if (isMigrationEnabled()) {
+            // [KeyguardStatusBarViewModel] will automatically handle dozing.
+            return;
+        }
         mDozing = dozing;
     }
 
@@ -502,6 +514,10 @@
         if (!isKeyguardShowing()) {
             return;
         }
+        if (isMigrationEnabled()) {
+            // [KeyguardStatusBarViewBinder] will handle view state updates.
+            return;
+        }
 
         float alphaQsExpansion = 1 - Math.min(
                 1, mShadeViewStateProvider.getLockscreenShadeDragProgress() * 2);
@@ -539,6 +555,11 @@
      * Updates the {@link KeyguardStatusBarView} state based on the provided values.
      */
     public void updateViewState(float alpha, int visibility) {
+        if (isMigrationEnabled()) {
+            // [KeyguardStatusBarViewBinder] will handle view state updates.
+            return;
+        }
+
         if (mDisableStateTracker.isDisabled()) {
             visibility = View.INVISIBLE;
         }
@@ -646,10 +667,19 @@
      * @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
      */
     public void setAlpha(float alpha) {
+        if (isMigrationEnabled()) {
+            // [KeyguardStatusBarViewBinder] will handle view state updates.
+            return;
+        }
+
         mExplicitAlpha = alpha;
         updateViewState();
     }
 
+    private boolean isMigrationEnabled() {
+        return mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW);
+    }
+
     private final ContentObserver mVolumeSettingObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean selfChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
new file mode 100644
index 0000000..c63ef9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView
+import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel
+
+/** Binds [KeyguardStatusBarViewModel] to [KeyguardStatusBarView]. */
+object KeyguardStatusBarViewBinder {
+    @JvmStatic
+    fun bind(
+        view: KeyguardStatusBarView,
+        viewModel: KeyguardStatusBarViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.isVisible.collect { isVisible ->
+                    view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
new file mode 100644
index 0000000..ddfed87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.StatusBarState
+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.stateIn
+
+/**
+ * A view model for the status bar displayed on keyguard (lockscreen).
+ *
+ * Note: This view model is for the status bar view as a whole. Certain icons may have their own
+ * individual view models, such as
+ * [com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel] or
+ * [com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel].
+ */
+@SysUISingleton
+class KeyguardStatusBarViewModel
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    /** True if this view should be visible and false otherwise. */
+    val isVisible: StateFlow<Boolean> =
+        combine(
+                keyguardInteractor.isDozing,
+                keyguardInteractor.statusBarState,
+            ) { isDozing, statusBarState ->
+                !isDozing && statusBarState == StatusBarState.KEYGUARD
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3c6d90d..727d649 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -135,7 +135,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
-import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -305,8 +304,6 @@
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
     private final FeatureFlags mFeatureFlags;
-    private final SecureSettings mSecureSettings;
-    private int mDialogTimeoutMillis;
 
     public VolumeDialogImpl(
             Context context,
@@ -323,8 +320,7 @@
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            FeatureFlags featureFlags,
-            SecureSettings secureSettings) {
+            FeatureFlags featureFlags) {
         mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -355,8 +351,6 @@
         mUseBackgroundBlur =
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
-        mSecureSettings = secureSettings;
-        mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
 
         dumpManager.registerDumpable("VolumeDialogImpl", this);
 
@@ -521,8 +515,6 @@
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setAlpha(0);
-        mDialogTimeoutMillis = mSecureSettings.getInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
-                DIALOG_TIMEOUT_MILLIS);
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -535,7 +527,7 @@
                     .alpha(1)
                     .translationX(0)
                     .setDuration(mDialogShowAnimationDurationMs)
-                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis))
+                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS))
                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                     .withEndAction(() -> {
                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -1522,7 +1514,7 @@
                     AccessibilityManager.FLAG_CONTENT_TEXT
                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
         }
-        return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis,
+        return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 624691b..cc9f3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -64,8 +63,7 @@
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            FeatureFlags featureFlags,
-            SecureSettings secureSettings) {
+            FeatureFlags featureFlags) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -81,8 +79,7 @@
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                featureFlags,
-                secureSettings);
+                featureFlags);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 2b1ce0e..05842a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -49,14 +49,22 @@
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.shade.ShadeViewStateProvider;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -66,6 +74,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -79,6 +88,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -133,16 +144,37 @@
     private KeyguardStatusBarView mKeyguardStatusBarView;
     private KeyguardStatusBarViewController mController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+    private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+    private KeyguardInteractor mKeyguardInteractor;
+    private KeyguardStatusBarViewModel mViewModel;
 
     @Before
     public void setup() throws Exception {
         mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
         mShadeViewStateProvider = new TestShadeViewStateProvider();
+        mShadeViewStateProvider = new TestShadeViewStateProvider();
 
         MockitoAnnotations.initMocks(this);
 
         when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
 
+        mKeyguardInteractor = new KeyguardInteractor(
+                mKeyguardRepository,
+                mCommandQueue,
+                mFeatureFlags,
+                mSceneTestUtils.getSceneContainerFlags(),
+                mSceneTestUtils.getDeviceEntryRepository(),
+                new FakeKeyguardBouncerRepository(),
+                new FakeConfigurationRepository(),
+                new FakeShadeRepository(),
+                () -> mSceneTestUtils.sceneInteractor());
+        mViewModel =
+                new KeyguardStatusBarViewModel(
+                        mTestScope.getBackgroundScope(),
+                        mKeyguardInteractor);
+
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
             mKeyguardStatusBarView =
@@ -169,6 +201,7 @@
                 mKeyguardStateController,
                 mKeyguardBypassController,
                 mKeyguardUpdateMonitor,
+                mViewModel,
                 mBiometricUnlockController,
                 mStatusBarStateController,
                 mStatusBarContentInsetsProvider,
@@ -455,6 +488,94 @@
     }
 
     @Test
+    public void updateViewState_dozingTrue_flagOff_viewHidden() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+
+        mController.setDozing(true);
+        mController.updateViewState();
+
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void updateViewState_dozingFalse_flagOff_viewShown() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+
+        mController.setDozing(false);
+        mController.updateViewState();
+
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateViewState_flagOn_doesNothing() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+
+        mKeyguardStatusBarView.setVisibility(View.GONE);
+        mKeyguardStatusBarView.setAlpha(0.456f);
+
+        mController.updateViewState();
+
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
+    }
+
+    @Test
+    public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+
+        mKeyguardStatusBarView.setVisibility(View.GONE);
+        mKeyguardStatusBarView.setAlpha(0.456f);
+
+        mController.updateViewState(0.789f, View.VISIBLE);
+
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
+    }
+
+    @Test
+    public void setAlpha_flagOn_doesNothing() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+
+        mKeyguardStatusBarView.setAlpha(0.456f);
+
+        mController.setAlpha(0.123f);
+
+        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
+    }
+
+    @Test
+    public void setDozing_flagOn_doesNothing() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+        mController.init();
+        mController.onViewAttached();
+        updateStateToKeyguard();
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.setDozing(true);
+        mController.updateViewState();
+
+        // setDozing(true) should typically cause the view to hide. But since the flag is on, we
+        // should ignore these set dozing calls and stay the same visibility.
+        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void setAlpha_explicitAlpha_setsExplicitAlpha() {
         mController.onViewAttached();
         updateStateToKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
new file mode 100644
index 0000000..f8ec8d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.FakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.StatusBarState
+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.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class KeyguardStatusBarViewModelTest : SysuiTestCase() {
+    private val testScope = TestScope()
+    private val sceneTestUtils = SceneTestUtils(this)
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val keyguardInteractor =
+        KeyguardInteractor(
+            keyguardRepository,
+            mock<CommandQueue>(),
+            FakeFeatureFlagsClassic(),
+            sceneTestUtils.sceneContainerFlags,
+            sceneTestUtils.deviceEntryRepository,
+            FakeKeyguardBouncerRepository(),
+            FakeConfigurationRepository(),
+            FakeShadeRepository()
+        ) {
+            sceneTestUtils.sceneInteractor()
+        }
+
+    private val underTest =
+        KeyguardStatusBarViewModel(
+            testScope.backgroundScope,
+            keyguardInteractor,
+        )
+
+    @Test
+    fun isVisible_dozing_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isVisible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            keyguardRepository.setIsDozing(true)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isVisible_statusBarStateShade_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isVisible_statusBarStateShadeLocked_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isVisible_statusBarStateKeyguard_andNotDozing_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            keyguardRepository.setIsDozing(false)
+
+            assertThat(latest).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index daf8877..28fc5db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -42,7 +42,6 @@
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.SystemClock;
-import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -71,7 +70,6 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
-import com.android.systemui.util.settings.FakeSettings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -136,8 +134,6 @@
     private FakeFeatureFlags mFeatureFlags;
     private int mLongestHideShowAnimationDuration = 250;
 
-    private FakeSettings mSecureSettings;
-
     @Rule
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
@@ -166,8 +162,6 @@
 
         mFeatureFlags = new FakeFeatureFlags();
 
-        mSecureSettings = new FakeSettings();
-
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -183,8 +177,7 @@
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFeatureFlags,
-                mSecureSettings);
+                mFeatureFlags);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -249,18 +242,6 @@
     }
 
     @Test
-    public void testSetTimeoutValue_ComputeTimeout() {
-        mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000);
-        Mockito.reset(mAccessibilityMgr);
-        mDialog.init(0, null);
-        mDialog.rescheduleTimeoutH();
-        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
-                7000,
-                AccessibilityManager.FLAG_CONTENT_CONTROLS);
-    }
-
-
-    @Test
     public void testComputeTimeout_tooltip() {
         Mockito.reset(mAccessibilityMgr);
         mDialog.showCaptionsTooltip();
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index caf1684..40b29d7 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -340,6 +340,10 @@
     // A map of preloaded package names and the path to its app metadata file path.
     private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>();
 
+    // A set of pre-installed package names that requires strict signature verification once
+    // updated to avoid cached/potentially tampered results.
+    private final Set<String> mPreinstallPackagesWithStrictSignatureCheck = new ArraySet<>();
+
     /**
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
@@ -542,6 +546,10 @@
         return mAppMetadataFilePaths;
     }
 
+    public Set<String> getPreinstallPackagesWithStrictSignatureCheck() {
+        return mPreinstallPackagesWithStrictSignatureCheck;
+    }
+
     /**
      * Only use for testing. Do NOT use in production code.
      * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1485,6 +1493,17 @@
                             mAppMetadataFilePaths.put(packageName, path);
                         }
                     } break;
+                    case "require-strict-signature": {
+                        if (android.security.Flags.extendVbChainToUpdatedApk()) {
+                            String packageName = parser.getAttributeValue(null, "package");
+                            if (TextUtils.isEmpty(packageName)) {
+                                Slog.w(TAG, "<" + name + "> without valid package in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                            } else {
+                                mPreinstallPackagesWithStrictSignatureCheck.add(packageName);
+                            }
+                        }
+                    } break;
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 4ef2f1e..3d347be 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1025,10 +1025,12 @@
             handles.add(id);
         }
 
-        // If the device is running in headless system user mode then allow
-        // User 0 to access camera.
-        if (UserManager.isHeadlessSystemUserMode()) {
-            handles.add(UserHandle.USER_SYSTEM);
+        if (Flags.cameraHsumPermission()) {
+            // If the device is running in headless system user mode then allow
+            // User 0 to access camera.
+            if (UserManager.isHeadlessSystemUserMode()) {
+                handles.add(UserHandle.USER_SYSTEM);
+            }
         }
 
         return handles;
diff --git a/services/core/java/com/android/server/integrity/TEST_MAPPING b/services/core/java/com/android/server/integrity/TEST_MAPPING
index 4959204..be8d2e1 100644
--- a/services/core/java/com/android/server/integrity/TEST_MAPPING
+++ b/services/core/java/com/android/server/integrity/TEST_MAPPING
@@ -10,6 +10,7 @@
     },
     {
       "name": "GtsSecurityHostTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "include-filter": "com.google.android.security.gts.AppIntegrityManagerTest"
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8f71a9b..c6388e7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3728,7 +3728,7 @@
         final ScanResult scanResult = scanResultPair.first;
         boolean shouldHideSystemApp = scanResultPair.second;
         final InstallRequest installRequest = new InstallRequest(
-                parsedPackage, parseFlags, scanFlags, user, scanResult);
+                parsedPackage, parseFlags, scanFlags, user, scanResult, disabledPkgSetting);
 
         String existingApexModuleName = null;
         synchronized (mPm.mLock) {
@@ -3962,6 +3962,7 @@
         final String disabledPkgName = pkgAlreadyExists
                 ? pkgSetting.getPackageName() : parsedPackage.getPackageName();
         final boolean isSystemPkgUpdated;
+        final PackageSetting disabledPkgSetting;
         final boolean isUpgrade;
         synchronized (mPm.mLock) {
             isUpgrade = mPm.isDeviceUpgrading();
@@ -3975,8 +3976,7 @@
                         + "and install it as non-updated system app.");
                 mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
             }
-            final PackageSetting disabledPkgSetting =
-                    mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
+            disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
             isSystemPkgUpdated = disabledPkgSetting != null;
 
             if (DEBUG_INSTALL && isSystemPkgUpdated) {
@@ -4048,6 +4048,23 @@
         // equal to the version on the /data partition. Throw an exception and use
         // the application already installed on the /data partition.
         if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+            // For some updated system packages, during addForInit we want to ensure the
+            // PackageSetting has the correct SigningDetails compares to the original version on
+            // the system partition. For the check to happen later during the /data scan, update
+            // the disabled package setting per the original APK on a system partition so that it
+            // can be trusted during reconcile.
+            if (needSignatureMatchToSystem(parsedPackage.getPackageName())) {
+                final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                final ParseResult<SigningDetails> result =
+                        ParsingPackageUtils.getSigningDetails(input, parsedPackage,
+                                false /*skipVerify*/);
+                if (result.isError()) {
+                    throw new PrepareFailure("Failed collect during scanSystemPackageLI",
+                            result.getException());
+                }
+                disabledPkgSetting.setSigningDetails(result.getResult());
+            }
+
             // In the case of a skipped package, commitReconciledScanResultLocked is not called to
             // add the object to the "live" data structures, so this is the final mutation step
             // for the package. Which means it needs to be finalized here to cache derived fields.
@@ -4065,19 +4082,16 @@
         // Verify certificates against what was last scanned. Force re-collecting certificate in two
         // special cases:
         // 1) when scanning system, force re-collect only if system is upgrading.
-        // 2) when scanning /data, force re-collect only if the app is privileged (updated from
-        // preinstall, or treated as privileged, e.g. due to shared user ID).
+        // 2) when scanning /data, force re-collect only if the package name is allowlisted.
         final boolean forceCollect = scanSystemPartition ? isUpgrade
-                : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting);
+                : pkgAlreadyExists && needSignatureMatchToSystem(pkgSetting.getPackageName());
         if (DEBUG_VERIFY && forceCollect) {
             Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
         }
 
-        // Full APK verification can be skipped during certificate collection, only if the file is
-        // in verified partition, or can be verified on access (when apk verity is enabled). In both
-        // cases, only data in Signing Block is verified instead of the whole file.
-        final boolean skipVerify = scanSystemPartition
-                || (forceCollect && canSkipForcedPackageVerification(parsedPackage));
+        // APK verification can be skipped during certificate collection, only if the file is in a
+        // verified partition.
+        final boolean skipVerify = scanSystemPartition;
         ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
                 mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
                 mPm.isPreNMR1Upgrade());
@@ -4196,22 +4210,15 @@
     }
 
     /**
-     * Returns if forced apk verification can be skipped for the whole package, including splits.
+     * Returns whether the package needs a signature verification against the pre-installed version
+     * at boot.
      */
-    private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
-        if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
+    private boolean needSignatureMatchToSystem(String packageName) {
+        if (!android.security.Flags.extendVbChainToUpdatedApk()) {
             return false;
         }
-        // TODO: Allow base and splits to be verified individually.
-        String[] splitCodePaths = pkg.getSplitCodePaths();
-        if (!ArrayUtils.isEmpty(splitCodePaths)) {
-            for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!VerityUtils.hasFsverity(splitCodePaths[i])) {
-                    return false;
-                }
-            }
-        }
-        return true;
+        return mPm.mInjector.getSystemConfig().getPreinstallPackagesWithStrictSignatureCheck()
+            .contains(packageName);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index a4ee3c8..1c7024b 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -58,7 +58,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
 
@@ -190,7 +189,7 @@
 
     // addForInit
     InstallRequest(ParsedPackage parsedPackage, int parseFlags, int scanFlags,
-            @Nullable UserHandle user, ScanResult scanResult) {
+            @Nullable UserHandle user, ScanResult scanResult, PackageSetting disabledPs) {
         if (user != null) {
             mUserId = user.getIdentifier();
         } else {
@@ -206,6 +205,7 @@
         mPackageMetrics = null; // No logging from this code path
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
+        mDisabledPs = disabledPs;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 8e91f42..bcb7bde 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -521,11 +521,8 @@
     }
 
     /**
-     * Make sure the updated priv app is signed with the same key as the original APK file on the
-     * /system partition.
-     *
-     * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
-     * and is not tamperproof.
+     * Verifies the updated system app has a signature that is consistent with the pre-installed
+     * version or the signing lineage.
      */
     private static boolean matchSignatureInSystem(@NonNull String packageName,
             @NonNull SigningDetails signingDetails, PackageSetting disabledPkgSetting) {
@@ -559,17 +556,12 @@
                         == FSVERITY_ENABLED;
     }
 
-    /** Returns true to force apk verification if the package is considered privileged. */
-    static boolean isApkVerificationForced(@Nullable PackageSetting ps) {
-        // TODO(b/154310064): re-enable.
-        return false;
-    }
-
     /**
      * Verifies that signatures match.
      * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
      * @throws PackageManagerException if the signatures did not match.
      */
+    @SuppressWarnings("ReferenceEquality")
     public static boolean verifySignatures(PackageSetting pkgSetting,
             @Nullable SharedUserSetting sharedUserSetting,
             PackageSetting disabledPkgSetting, SigningDetails parsedSignatures,
@@ -578,13 +570,23 @@
         final String packageName = pkgSetting.getPackageName();
         boolean compatMatch = false;
         if (pkgSetting.getSigningDetails().getSignatures() != null) {
-            // Already existing package. Make sure signatures match
+            // For an already existing package, make sure the parsed signatures from the package
+            // match the one in PackageSetting.
             boolean match = parsedSignatures.checkCapability(
                     pkgSetting.getSigningDetails(),
                     SigningDetails.CertCapabilities.INSTALLED_DATA)
                             || pkgSetting.getSigningDetails().checkCapability(
                                     parsedSignatures,
                                     SigningDetails.CertCapabilities.ROLLBACK);
+            // Also make sure the parsed signatures are consistent with the disabled package
+            // setting, if any. The additional UNKNOWN check is because disabled package settings
+            // may not have SigningDetails currently, and we don't want to cause an uninstall.
+            if (android.security.Flags.extendVbChainToUpdatedApk()
+                    && match && disabledPkgSetting != null
+                    && disabledPkgSetting.getSigningDetails() != SigningDetails.UNKNOWN) {
+                match = matchSignatureInSystem(packageName, parsedSignatures, disabledPkgSetting);
+            }
+
             if (!match && compareCompat) {
                 match = matchSignaturesCompat(packageName, pkgSetting.getSignatures(),
                         parsedSignatures);
@@ -603,11 +605,6 @@
                                         SigningDetails.CertCapabilities.ROLLBACK);
             }
 
-            if (!match && isApkVerificationForced(disabledPkgSetting)) {
-                match = matchSignatureInSystem(packageName, pkgSetting.getSigningDetails(),
-                        disabledPkgSetting);
-            }
-
             if (!match && isRollback) {
                 // Since a rollback can only be initiated for an APK previously installed on the
                 // device allow rolling back to a previous signing key even if the rollback
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index c263978..6e3b538 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -27,6 +27,7 @@
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.PACKAGE_INFO_GID;
 import static android.os.Process.SYSTEM_UID;
+
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
 import static com.android.server.pm.SharedUidMigration.BEST_EFFORT;
@@ -5211,7 +5212,7 @@
 
             File dataDir = PackageInfoUtils.getDataDir(ps, user.id);
             pw.print("      dataDir=");
-            pw.println(dataDir.getAbsolutePath());
+            pw.println(dataDir == null ? "null" : dataDir.getAbsolutePath());
 
             final PackageUserStateInternal pus = ps.readUserState(user.id);
             pw.print("      firstInstallTime=");
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 0fd7a37..e12b70f 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -64,6 +64,7 @@
     },
     {
       "name": "GtsSecurityHostTestCases",
+      "keywords": ["internal"],
       "options": [
         {
           "include-filter": "com.google.android.security.gts.PackageVerifierTest"
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 31856f1..f4f03f4 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -544,11 +544,6 @@
      */
     public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
         try {
-            final String packageName = pkg.getPackageName();
-            final String apkPath = pkg.getSplits().get(0).getPath();
-            // TODO(b/143971007): Use a cross-user directory
-            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
-            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
                     || pkg.isDefaultToDeviceProtectedStorage()) {
                 // Privileged apps prefer to load trusted code so they don't use compiled views.
@@ -558,6 +553,14 @@
                 // selinux permissions required for writing to user_de.
                 return false;
             }
+            final String packageName = pkg.getPackageName();
+            final String apkPath = pkg.getSplits().get(0).getPath();
+            final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+            if (dataDir == null) {
+                // The app is not installed on the target user and doesn't have a data dir
+                return false;
+            }
+            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                     ") to " + outDexFile);
             final long callingId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
index 6405ea5..00269224 100644
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
@@ -40,8 +40,11 @@
     public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
         try {
             final String packageName = ps.getPackageName();
-            // TODO(b/143971007): Use a cross-user directory
             File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+            if (dataDir == null) {
+                // The app is not installed on the target user and doesn't have a data dir
+                return false;
+            }
             final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                 ") to " + outDexFile);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fac98b8..774be9e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1545,6 +1545,17 @@
             mTargetDisplays.add(dc);
         }
 
+        for (int i = 0; i < mTargets.size(); ++i) {
+            final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
+            if (da == null) continue;
+            if (da.isVisibleRequested()) {
+                mController.mValidateDisplayVis.remove(da);
+            } else {
+                // In case something accidentally hides a displayarea and nothing shows it again.
+                mController.mValidateDisplayVis.add(da);
+            }
+        }
+
         if (mOverrideOptions != null) {
             info.setAnimationOptions(mOverrideOptions);
             if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7d2933a..c92a781 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -150,6 +150,13 @@
     final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>();
 
     /**
+     * List of display areas which were last sent as "closing"-type and haven't yet had a
+     * corresponding "opening"-type transition. A mismatch here is usually related to issues in
+     * keyguard unlock.
+     */
+    final ArrayList<DisplayArea> mValidateDisplayVis = new ArrayList<>();
+
+    /**
      * Currently playing transitions (in the order they were started). When finished, records are
      * removed from this list.
      */
@@ -941,6 +948,15 @@
             ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y);
         }
         mValidateActivityCompat.clear();
+        for (int i = 0; i < mValidateDisplayVis.size(); ++i) {
+            final DisplayArea da = mValidateDisplayVis.get(i);
+            if (!da.isAttached() || da.getSurfaceControl() == null) continue;
+            if (da.isVisibleRequested()) {
+                Slog.e(TAG, "DisplayArea became visible outside of a transition: " + da);
+                da.getSyncTransaction().show(da.getSurfaceControl());
+            }
+        }
+        mValidateDisplayVis.clear();
     }
 
     /**
diff --git a/services/foldables/devicestateprovider/proguard.flags b/services/foldables/devicestateprovider/proguard.flags
new file mode 100644
index 0000000..069cbc6
--- /dev/null
+++ b/services/foldables/devicestateprovider/proguard.flags
@@ -0,0 +1 @@
+-keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy { *; }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
new file mode 100644
index 0000000..5f2cf3c
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
@@ -0,0 +1,140 @@
+/*
+ * 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.policy;
+
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
+import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+
+import com.android.server.devicestate.DeviceStatePolicy;
+import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+
+/**
+ * Device state policy for a foldable device that supports tent mode: a mode when the device
+ * keeps the outer display on until reaching a certain hinge angle threshold.
+ *
+ * Contains configuration for {@link FoldableDeviceStateProvider}.
+ */
+public class TentModeDeviceStatePolicy extends DeviceStatePolicy {
+
+    private static final int DEVICE_STATE_CLOSED = 0;
+    private static final int DEVICE_STATE_HALF_OPENED = 1;
+    private static final int DEVICE_STATE_OPENED = 2;
+    private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3;
+    private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+
+    private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
+    private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
+    private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
+    private static final int MAX_CLOSED_ANGLE_DEGREES = 5;
+
+    private final DeviceStateProvider mProvider;
+
+    /**
+     * Creates TentModeDeviceStatePolicy
+     *
+     * @param context           Android context
+     * @param hingeAngleSensor  hinge angle sensor that will be used to switch between states
+     * @param hallSensor        hall sensor that will be used to switch between states
+     * @param closeAngleDegrees if non-zero, this angle will be used as a threshold to switch
+     *                          between folded and unfolded modes, otherwise when folding the
+     *                          display switch will happen at 0 degrees
+     */
+    public TentModeDeviceStatePolicy(@NonNull Context context,
+            @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) {
+        super(context);
+
+        final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+        final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees);
+
+        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
+                hallSensor, displayManager, configuration);
+    }
+
+    private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) {
+        return new DeviceStateConfiguration[]{
+                createClosedConfiguration(closeAngleDegrees),
+                createConfig(DEVICE_STATE_HALF_OPENED,
+                        /* name= */ "HALF_OPENED",
+                        (provider) -> {
+                            final float hingeAngle = provider.getHingeAngle();
+                            return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
+                                    && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
+                        }),
+                createConfig(DEVICE_STATE_OPENED,
+                        /* name= */ "OPENED",
+                        (provider) -> true),
+                createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
+                        /* name= */ "REAR_DISPLAY_STATE",
+                        /* flags= */ FLAG_EMULATED_ONLY,
+                        (provider) -> false),
+                createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT,
+                        /* name= */ "CONCURRENT_INNER_DEFAULT",
+                        /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP
+                                | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                        (provider) -> false)
+        };
+    }
+
+    private DeviceStateConfiguration createClosedConfiguration(int closeAngleDegrees) {
+        if (closeAngleDegrees > 0) {
+            // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
+            return createConfig(
+                    DEVICE_STATE_CLOSED,
+                    /* name= */ "CLOSED",
+                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
+                    (provider) -> {
+                        final float hingeAngle = provider.getHingeAngle();
+                        return hingeAngle <= closeAngleDegrees;
+                    }
+            );
+        }
+
+        // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
+        // angle when switching to the inner display
+        return createTentModeClosedState(DEVICE_STATE_CLOSED,
+                /* name= */ "CLOSED",
+                /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
+                MIN_CLOSED_ANGLE_DEGREES,
+                MAX_CLOSED_ANGLE_DEGREES,
+                TENT_MODE_SWITCH_ANGLE_DEGREES);
+    }
+
+    @Override
+    public DeviceStateProvider getDeviceStateProvider() {
+        return mProvider;
+    }
+
+    @Override
+    public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
+        onComplete.run();
+    }
+}
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index 24323c8..00bfcd3 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -1,4 +1,17 @@
 {
+    "platinum-test": [
+        {
+            "name": "CtsPermissionTestCases",
+            "options": [
+                {
+                    "include-annotation": "android.platform.test.annotations.PlatinumTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ]
+        }
+    ],
     "presubmit": [
         {
             "name": "CtsPermissionTestCases",
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 6c39275..b8f726b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -224,7 +224,7 @@
         val scanRequest = ScanRequest(parsedPackage, null, null, null, null,
             null, null, null, 0, 0, false, null, null)
         val scanResult = ScanResult(scanRequest, null, null, false, 0, null, null, null)
-        var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
+        var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult, null)
 
         val latestInfoSetting =
             mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(installRequest)!!
@@ -309,7 +309,7 @@
         val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
         val scanResult = ScanResult(mock(), null, null,
             false, 0, null, testInfo, null)
-        var installRequest = InstallRequest(mock(), 0, 0, UserHandle(0), scanResult)
+        var installRequest = InstallRequest(mock(), 0, 0, UserHandle(0), scanResult, null)
 
         val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)
 
@@ -332,7 +332,7 @@
             null, null, null, 0, 0, false, null, null)
         val scanResult = ScanResult(scanRequest, packageSetting, null,
             false, 0, null, null, listOf(testInfo))
-        var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
+        var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult, null)
 
         val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)