Merge "Declare that KeyguardShadeMigrationNssl depends on keyguardBottomAreaRefactor" into main
diff --git a/OWNERS b/OWNERS
index 023bdef..733157f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -37,3 +37,5 @@
 
 per-file *ravenwood* = file:ravenwood/OWNERS
 per-file *Ravenwood* = file:ravenwood/OWNERS
+
+per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS
index 9452ea3..48a0201 100644
--- a/PERFORMANCE_OWNERS
+++ b/PERFORMANCE_OWNERS
@@ -3,3 +3,6 @@
 dualli@google.com
 carmenjackson@google.com
 philipcuadra@google.com
+shayba@google.com
+jdduke@google.com
+shombert@google.com
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 7e07e1f..fc8523e 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1599,7 +1599,7 @@
      * fully removed, otherwise system resources may leak.
      * @hide
      */
-    public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
+    public static final native boolean sendSignalToProcessGroup(int uid, int pid, int signal);
 
     /**
       * Freeze the cgroup for the given UID.
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 91dfc60..43e0c34 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1238,7 +1238,7 @@
     return killProcessGroup(uid, pid, SIGKILL);
 }
 
-jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
+jboolean android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
                                                  jint signal) {
     return sendSignalToProcessGroup(uid, pid, signal);
 }
@@ -1310,7 +1310,7 @@
         //{"setApplicationObject", "(Landroid/os/IBinder;)V",
         //(void*)android_os_Process_setApplicationObject},
         {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
-        {"sendSignalToProcessGroup", "(III)I", (void*)android_os_Process_sendSignalToProcessGroup},
+        {"sendSignalToProcessGroup", "(III)Z", (void*)android_os_Process_sendSignalToProcessGroup},
         {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
         {"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
         {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index b4b3e92..4ec5e1b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -26,7 +26,6 @@
 import android.os.ServiceSpecificException;
 import android.os.StrictMode;
 import android.security.authorization.IKeystoreAuthorization;
-import android.security.authorization.LockScreenEvent;
 import android.system.keystore2.ResponseCode;
 import android.util.Log;
 
@@ -76,26 +75,37 @@
     }
 
     /**
-     * Informs keystore2 about lock screen event.
+     * Tells Keystore that the device is now unlocked for a user.
      *
-     * @param locked            - whether it is a lock (true) or unlock (false) event
-     * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic
-     *                            password provided by the LockSettingService
-     * @param unlockingSids     - KeyMint secure user IDs that should be permitted to unlock
-     *                            UNLOCKED_DEVICE_REQUIRED keys.
-     *
+     * @param userId - the user's Android user ID
+     * @param password - a secret derived from the user's synthetic password, if the unlock method
+     *                   is LSKF (or equivalent) and thus has made the synthetic password available
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId,
-            @Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) {
+    public static int onDeviceUnlocked(int userId, @Nullable byte[] password) {
         StrictMode.noteDiskWrite();
         try {
-            if (locked) {
-                getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids);
-            } else {
-                getService().onLockScreenEvent(
-                        LockScreenEvent.UNLOCK, userId, syntheticPassword, unlockingSids);
-            }
+            getService().onDeviceUnlocked(userId, password);
+            return 0;
+        } catch (RemoteException | NullPointerException e) {
+            Log.w(TAG, "Can not connect to keystore", e);
+            return SYSTEM_ERROR;
+        } catch (ServiceSpecificException e) {
+            return e.errorCode;
+        }
+    }
+
+    /**
+     * Tells Keystore that the device is now locked for a user.
+     *
+     * @param userId - the user's Android user ID
+     * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+     * @return 0 if successful or a {@code ResponseCode}.
+     */
+    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+        StrictMode.noteDiskWrite();
+        try {
+            getService().onDeviceLocked(userId, unlockingSids);
             return 0;
         } catch (RemoteException | NullPointerException e) {
             Log.w(TAG, "Can not connect to keystore", e);
diff --git a/packages/SystemUI/docs/executors.md b/packages/SystemUI/docs/executors.md
index 8520ce2..2d9438c 100644
--- a/packages/SystemUI/docs/executors.md
+++ b/packages/SystemUI/docs/executors.md
@@ -14,10 +14,10 @@
 [FakeExecutor][FakeExecutor] is available.
 
 [Executor]: https://developer.android.com/reference/java/util/concurrent/Executor.html
-[Handler]: https://developer.android.com/reference/android/os/Handler
+[Handler]: https://developer.android.com/reference/android/os/Handler.html
 [Runnable]: https://developer.android.com/reference/java/lang/Runnable.html
 [DelayableExecutor]: /packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java
-[FakeExecutor]: /packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+[FakeExecutor]: /packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
 
 ## Rationale
 
@@ -117,7 +117,7 @@
 postDelayed() | `none`    | executeDelayed()
 postAtTime()  | `none`    | executeAtTime()
 
-There is one notable gap in this implementation: `Handler.postAtFrontOfQueue()`.
+There are some notable gaps in this implementation: `Handler.postAtFrontOfQueue()`.
 If you require this method, or similar, please reach out. The idea of a
 PriorityQueueExecutor has been floated, but will not be implemented until there
 is a clear need.
@@ -173,13 +173,20 @@
 
 If you feel that you have a use case that this does not cover, please reach out.
 
+### ContentObserver
+
+One notable place where Handlers have been a requirement in the past is with
+[ContentObserver], which takes a Handler as an argument. However, we have created
+[ExecutorContentObserver], which is a hidden API that accepts an [Executor] in its
+constructor instead of a [Handler], and is otherwise identical.
+
+[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver.html
+[ExecutorContentObserver]: /core/java/android/database/ExecutorContentObserver.java
+
 ### Handlers Are Still Necessary
 
-Handlers aren't going away. There are Android APIs that still require them (even
-if future API development discourages them). A simple example is
-[ContentObserver][ContentObserver]. Use them where necessary.
-
-[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver
+Handlers aren't going away. There are other Android APIs that still require them.
+Avoid Handlers when possible, but use them where necessary.
 
 ## Testing (FakeExecutor)
 
@@ -314,6 +321,15 @@
 The Runnables _will not_ interleave. All of one Executor's callbacks will run,
 then all of the other's.
 
+### Testing Handlers without Loopers
+
+If a [Handler] is required because it is used by Android APIs, but is only
+used in simple ways (i.e. just `Handler.post(Runnable)`), you may still
+want the benefits of [FakeExecutor] when writing your tests, which
+you can get by wrapping the [Executor] in a mock for testing. This can be
+done with `com.android.systemui.util.concurrency.mockExecutorHandler` in
+`MockExecutorHandler.kt`.
+
 ### TestableLooper.RunWithLooper
 
 As long as you're using FakeExecutors in all the code under test (and no
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 661c345..2a02164 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -313,6 +313,59 @@
         }
 
     @Test
+    fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+
+            assertThat(isAutoConfirmEnabled).isFalse()
+        }
+
+    @Test
+    fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+            assertThat(isAutoConfirmEnabled).isTrue()
+        }
+
+    @Test
+    fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            val throttling by collectLastValue(underTest.throttling)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+            // The feature is enabled.
+            assertThat(isAutoConfirmEnabled).isTrue()
+
+            // Make many wrong attempts to trigger throttling.
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+            }
+            assertThat(throttling).isNotNull()
+
+            // Throttling disabled auto-confirm.
+            assertThat(isAutoConfirmEnabled).isFalse()
+
+            // Move the clock forward one more second, to completely finish the throttling period:
+            advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L)
+            assertThat(throttling).isNull()
+
+            // Auto-confirm is still disabled, because throttling occurred at least once in this
+            // session.
+            assertThat(isAutoConfirmEnabled).isFalse()
+
+            // Correct PIN and unlocks successfully, resetting the 'session'.
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
+
+            // Auto-confirm is re-enabled.
+            assertThat(isAutoConfirmEnabled).isTrue()
+        }
+
+    @Test
     fun throttling() =
         testScope.runTest {
             val throttling by collectLastValue(underTest.throttling)
@@ -350,6 +403,7 @@
                 )
 
             // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
+            val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS
             repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time ->
                 advanceTimeBy(1000)
                 assertThat(throttling)
@@ -358,8 +412,7 @@
                             failedAttemptCount =
                                 FakeAuthenticationRepository
                                     .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                            remainingSeconds =
-                                FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - (time + 1),
+                            remainingSeconds = throttleTimeoutSec - (time + 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 dd4ca92..bd84b28 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
@@ -60,14 +60,6 @@
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
     /**
-     * Whether the auto confirm feature is enabled for the currently-selected user.
-     *
-     * Note that the length of the PIN is also important to take into consideration, please see
-     * [hintedPinLength].
-     */
-    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
-
-    /**
      * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
      * in order to unlock the device.
      */
@@ -93,6 +85,17 @@
      */
     val throttling: MutableStateFlow<AuthenticationThrottlingModel?>
 
+    /** Whether throttling has occurred at least once since the last successful authentication. */
+    val hasThrottlingOccurred: MutableStateFlow<Boolean>
+
+    /**
+     * Whether the auto confirm feature is enabled for the currently-selected user.
+     *
+     * Note that the length of the PIN is also important to take into consideration, please see
+     * [hintedPinLength].
+     */
+    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
+
     /**
      * The currently-configured authentication method. This determines how the authentication
      * challenge needs to be completed in order to unlock an otherwise locked device.
@@ -172,11 +175,6 @@
     mobileConnectionsRepository: MobileConnectionsRepository,
 ) : AuthenticationRepository {
 
-    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
-        refreshingFlow(
-            initialValue = false,
-            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
-        )
     override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
 
     override val hintedPinLength: Int = 6
@@ -190,8 +188,13 @@
     override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
         MutableStateFlow(null)
 
-    private val selectedUserId: Int
-        get() = userRepository.getSelectedUserInfo().id
+    override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+        refreshingFlow(
+            initialValue = false,
+            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
+        )
 
     override val authenticationMethod: Flow<AuthenticationMethodModel> =
         combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
@@ -280,6 +283,9 @@
         }
     }
 
+    private val selectedUserId: Int
+        get() = userRepository.getSelectedUserInfo().id
+
     /**
      * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
      * invoked on a background thread every time the selected user is changed and every time a new
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 4e67771..7f8f887 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
@@ -95,15 +95,14 @@
      *
      * Note that the length of the PIN is also important to take into consideration, please see
      * [hintedPinLength].
-     *
-     * During throttling, this is always disabled (`false`).
      */
     val isAutoConfirmEnabled: StateFlow<Boolean> =
-        combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) {
+        combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) {
                 featureEnabled,
-                throttling ->
-                // Disable auto-confirm during throttling.
-                featureEnabled && throttling == null
+                hasThrottlingOccurred ->
+                // Disable auto-confirm if throttling occurred since the last successful
+                // authentication attempt.
+                featureEnabled && !hasThrottlingOccurred
             }
             .stateIn(
                 scope = applicationScope,
@@ -221,6 +220,7 @@
             repository.setThrottleDuration(
                 durationMs = authenticationResult.throttleDurationMs,
             )
+            repository.hasThrottlingOccurred.value = true
             startThrottlingCountdown()
         }
 
@@ -228,6 +228,8 @@
             // Since authentication succeeded, we should refresh throttling to make sure that our
             // state is completely reflecting the upstream source of truth.
             refreshThrottling()
+
+            repository.hasThrottlingOccurred.value = false
         }
 
         return if (authenticationResult.isSuccessful) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cbfd17ff..9fe5c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -214,7 +214,7 @@
     private fun listenForLockscreenToPrimaryBouncerDragging() {
         var transitionId: UUID? = null
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
-            shadeRepository.shadeModel
+            shadeRepository.legacyShadeExpansion
                 .sample(
                     combine(
                         transitionInteractor.startedKeyguardTransitionStep,
@@ -224,23 +224,23 @@
                     ),
                     ::toQuad
                 )
-                .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
+                .collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) ->
                     val id = transitionId
                     if (id != null) {
                         if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
                             // An existing `id` means a transition is started, and calls to
                             // `updateTransition` will control it until FINISHED or CANCELED
                             var nextState =
-                                if (shadeModel.expansionAmount == 0f) {
+                                if (shadeExpansion == 0f) {
                                     TransitionState.FINISHED
-                                } else if (shadeModel.expansionAmount == 1f) {
+                                } else if (shadeExpansion == 1f) {
                                     TransitionState.CANCELED
                                 } else {
                                     TransitionState.RUNNING
                                 }
                             transitionRepository.updateTransition(
                                 id,
-                                1f - shadeModel.expansionAmount,
+                                1f - shadeExpansion,
                                 nextState,
                             )
 
@@ -274,7 +274,7 @@
                         // integrated into KeyguardTransitionRepository
                         if (
                             keyguardState.to == KeyguardState.LOCKSCREEN &&
-                                shadeModel.isUserDragging &&
+                                shadeRepository.legacyShadeTracking.value &&
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
                         ) {
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 702386d..c12efe8 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
@@ -224,8 +224,8 @@
         configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
             .flatMapLatest { translationDistance ->
-                shadeRepository.shadeModel.map {
-                    if (it.expansionAmount == 0f) {
+                shadeRepository.legacyShadeExpansion.map {
+                    if (it == 0f) {
                         // Reset the translation value
                         0f
                     } else {
@@ -233,7 +233,7 @@
                         MathUtils.lerp(
                             translationDistance,
                             0,
-                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e94a3eb..2445bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,25 +15,14 @@
  */
 package com.android.systemui.shade.data.repository
 
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.model.ShadeModel
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
 
+/** Data for the shade, mostly related to expansion of the shade and quick settings. */
 interface ShadeRepository {
-    /** ShadeModel information regarding shade expansion events */
-    val shadeModel: Flow<ShadeModel>
-
     /**
      * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
      * Settings can be expanded without the full shade expansion.
@@ -167,34 +156,7 @@
 
 /** Business logic for shade interactions */
 @SysUISingleton
-class ShadeRepositoryImpl
-@Inject
-constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository {
-    override val shadeModel: Flow<ShadeModel> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : ShadeExpansionListener {
-                        override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
-                            // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
-                            // It is too noisy and produces extra events that consumers won't care
-                            // about
-                            val info =
-                                ShadeModel(
-                                    expansionAmount = event.fraction,
-                                    isExpanded = event.expanded,
-                                    isUserDragging = event.tracking
-                                )
-                            trySendWithFailureLogging(info, TAG, "updated shade expansion info")
-                        }
-                    }
-
-                val currentState = shadeExpansionStateManager.addExpansionListener(callback)
-                callback.onPanelExpansionChanged(currentState)
-
-                awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
-            }
-            .distinctUntilChanged()
-
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
deleted file mode 100644
index ce0f4283..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
+++ /dev/null
@@ -1,28 +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.shade.domain.model
-
-import android.annotation.FloatRange
-
-/** Information about shade (NotificationPanel) expansion */
-data class ShadeModel(
-    /** 0 when collapsed, 1 when fully expanded. */
-    @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
-    /** Whether the panel should be considered expanded */
-    val isExpanded: Boolean = false,
-    /** Whether the user is actively dragging the panel. */
-    val isUserDragging: Boolean = false,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index faffb3e..d23c85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityEvent;
@@ -29,6 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.stream.Stream;
 
@@ -39,21 +39,23 @@
  */
 public abstract class AlertingNotificationManager {
     private static final String TAG = "AlertNotifManager";
-    protected final Clock mClock = new Clock();
+    protected final SystemClock mSystemClock;
     protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
     protected final HeadsUpManagerLogger mLogger;
 
-    public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler) {
-        mLogger = logger;
-        mHandler = handler;
-    }
-
     protected int mMinimumDisplayTime;
-    protected int mStickyDisplayTime;
-    protected int mAutoDismissNotificationDecay;
+    protected int mStickyForSomeTimeAutoDismissTime;
+    protected int mAutoDismissTime;
     @VisibleForTesting
     public Handler mHandler;
 
+    public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler,
+            SystemClock systemClock) {
+        mLogger = logger;
+        mHandler = handler;
+        mSystemClock = systemClock;
+    }
+
     /**
      * Called when posting a new notification that should alert the user and appear on screen.
      * Adds the notification to be managed.
@@ -251,7 +253,7 @@
     public long getEarliestRemovalTime(String key) {
         AlertEntry alerting = mAlertEntries.get(key);
         if (alerting != null) {
-            return Math.max(0, alerting.mEarliestRemovaltime - mClock.currentTimeMillis());
+            return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
         }
         return 0;
     }
@@ -259,7 +261,7 @@
     protected class AlertEntry implements Comparable<AlertEntry> {
         @Nullable public NotificationEntry mEntry;
         public long mPostTime;
-        public long mEarliestRemovaltime;
+        public long mEarliestRemovalTime;
 
         @Nullable protected Runnable mRemoveAlertRunnable;
 
@@ -283,8 +285,8 @@
         public void updateEntry(boolean updatePostTime, @Nullable String reason) {
             mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
-            final long now = mClock.currentTimeMillis();
-            mEarliestRemovaltime = now + mMinimumDisplayTime;
+            final long now = mSystemClock.elapsedRealtime();
+            mEarliestRemovalTime = now + mMinimumDisplayTime;
 
             if (updatePostTime) {
                 mPostTime = Math.max(mPostTime, now);
@@ -318,7 +320,7 @@
          * @return true if the notification has been on screen long enough
          */
         public boolean wasShownLongEnough() {
-            return mEarliestRemovaltime < mClock.currentTimeMillis();
+            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
         }
 
         @Override
@@ -351,7 +353,7 @@
             if (mRemoveAlertRunnable != null) {
                 removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)");
 
-                final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis();
+                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
                 mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
             }
         }
@@ -361,22 +363,16 @@
          * @return the post time
          */
         protected long calculatePostTime() {
-            return mClock.currentTimeMillis();
+            return mSystemClock.elapsedRealtime();
         }
 
         /**
          * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealTime()}
+         * {@link SystemClock#elapsedRealtime()}
          */
         protected long calculateFinishTime() {
             // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
             return 0;
         }
     }
-
-    protected final static class Clock {
-        public long currentTimeMillis() {
-            return SystemClock.elapsedRealtime();
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3a95e6d..644c896 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -49,6 +49,8 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -115,11 +117,14 @@
             VisualStabilityProvider visualStabilityProvider,
             ConfigurationController configurationController,
             @Main Handler handler,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
             ShadeInteractor shadeInteractor) {
-        super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+        super(context, logger, handler, globalSettings, systemClock, accessibilityManagerWrapper,
+                uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
@@ -206,7 +211,7 @@
     @Override
     public boolean shouldSwallowClick(@NonNull String key) {
         BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
-        return entry != null && mClock.currentTimeMillis() < entry.mPostTime;
+        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
     }
 
     public void onExpandingFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index cec76f3..8054b04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -25,8 +25,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.view.accessibility.AccessibilityManager;
 
@@ -40,6 +38,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 
@@ -85,36 +85,40 @@
     public BaseHeadsUpManager(@NonNull final Context context,
             HeadsUpManagerLogger logger,
             @Main Handler handler,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger) {
-        super(logger, handler);
+        super(logger, handler, systemClock);
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
         Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
-        mStickyDisplayTime = resources.getInteger(R.integer.sticky_heads_up_notification_time);
-        mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+        mStickyForSomeTimeAutoDismissTime = resources.getInteger(
+                R.integer.sticky_heads_up_notification_time);
+        mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
         mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
         mSnoozedPackages = new ArrayMap<>();
         int defaultSnoozeLengthMs =
                 resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
 
-        mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
-                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+        mSnoozeLengthMs = globalSettings.getInt(SETTING_HEADS_UP_SNOOZE_LENGTH_MS,
+                defaultSnoozeLengthMs);
         ContentObserver settingsObserver = new ContentObserver(handler) {
             @Override
             public void onChange(boolean selfChange) {
-                final int packageSnoozeLengthMs = Settings.Global.getInt(
-                        context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+                final int packageSnoozeLengthMs = globalSettings.getInt(
+                        SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
                 if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
                     mSnoozeLengthMs = packageSnoozeLengthMs;
                     mLogger.logSnoozeLengthChange(packageSnoozeLengthMs);
                 }
             }
         };
-        context.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
+        globalSettings.registerContentObserver(
+                globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
+                /* notifyForDescendants = */ false,
                 settingsObserver);
     }
 
@@ -231,7 +235,7 @@
         final String key = snoozeKey(packageName, mUser);
         Long snoozedUntil = mSnoozedPackages.get(key);
         if (snoozedUntil != null) {
-            if (snoozedUntil > mClock.currentTimeMillis()) {
+            if (snoozedUntil > mSystemClock.elapsedRealtime()) {
                 mLogger.logIsSnoozedReturned(key);
                 return true;
             }
@@ -250,7 +254,7 @@
             String packageName = entry.mEntry.getSbn().getPackageName();
             String snoozeKey = snoozeKey(packageName, mUser);
             mLogger.logPackageSnoozed(snoozeKey);
-            mSnoozedPackages.put(snoozeKey, mClock.currentTimeMillis() + mSnoozeLengthMs);
+            mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
         }
     }
 
@@ -308,7 +312,7 @@
     protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
-        pw.print("  now="); pw.println(mClock.currentTimeMillis());
+        pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
         for (AlertEntry entry: mAlertEntries.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.mEntry);
@@ -519,12 +523,12 @@
 
         /**
          * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealTime()}
+         * {@link SystemClock#elapsedRealtime()}
          */
         @Override
         protected long calculateFinishTime() {
             final long duration = getRecommendedHeadsUpTimeoutMs(
-                    isStickyForSomeTime() ? mStickyDisplayTime : mAutoDismissNotificationDecay);
+                    isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
 
             return mPostTime + duration;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 575d8bf..fa17672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
 import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.model.ShadeModel
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.mock
@@ -83,23 +82,13 @@
 
     private fun TestComponent.shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 1f,
-                    isExpanded = true,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
             shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 0f,
-                    isExpanded = false,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
             shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(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 bf6d5c4..976dc5f 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
@@ -43,7 +43,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.model.ShadeModel
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -1329,12 +1328,8 @@
             // GIVEN the keyguard is showing locked
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             runCurrent()
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = .9f,
-                    isUserDragging = true,
-                )
-            )
+            shadeRepository.setLegacyShadeTracking(true)
+            shadeRepository.setLegacyShadeExpansion(.9f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1350,12 +1345,8 @@
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 1f,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeTracking(false)
+            shadeRepository.setLegacyShadeExpansion(1f)
             runCurrent()
 
             // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index f8aa359..750693c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -19,33 +19,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.model.ShadeModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.StandardTestDispatcher
 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.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeRepositoryImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -53,57 +40,10 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest = ShadeRepositoryImpl(shadeExpansionStateManager)
-        `when`(shadeExpansionStateManager.addExpansionListener(any()))
-            .thenReturn(ShadeExpansionChangeEvent(0f, false, false, 0f))
+        underTest = ShadeRepositoryImpl()
     }
 
     @Test
-    fun shadeExpansionChangeEvent() =
-        testScope.runTest {
-            var latest: ShadeModel? = null
-            val job = underTest.shadeModel.onEach { latest = it }.launchIn(this)
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(0f)
-            assertThat(latest?.isExpanded).isEqualTo(false)
-            assertThat(latest?.isUserDragging).isEqualTo(false)
-
-            val captor = withArgCaptor {
-                verify(shadeExpansionStateManager).addExpansionListener(capture())
-            }
-
-            captor.onPanelExpansionChanged(
-                ShadeExpansionChangeEvent(
-                    fraction = 1f,
-                    expanded = true,
-                    tracking = false,
-                    dragDownPxAmount = 0f,
-                )
-            )
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(1f)
-            assertThat(latest?.isExpanded).isEqualTo(true)
-            assertThat(latest?.isUserDragging).isEqualTo(false)
-
-            captor.onPanelExpansionChanged(
-                ShadeExpansionChangeEvent(
-                    fraction = .67f,
-                    expanded = false,
-                    tracking = true,
-                    dragDownPxAmount = 0f,
-                )
-            )
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(.67f)
-            assertThat(latest?.isExpanded).isEqualTo(false)
-            assertThat(latest?.isUserDragging).isEqualTo(true)
-
-            job.cancel()
-        }
-
-    @Test
     fun updateQsExpansion() =
         testScope.runTest {
             assertThat(underTest.qsExpansion.value).isEqualTo(0f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b98dc00..a3cff87e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -39,12 +39,15 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,18 +77,26 @@
     protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
     protected Handler mTestHandler;
+    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    protected final SystemClock mSystemClock = new SystemClockImpl();
     protected boolean mTimedOut = false;
 
     @Mock protected ExpandableNotificationRow mRow;
 
+    static {
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
+    }
+
     private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
         private AlertEntry mLastCreatedEntry;
 
-        private TestableAlertingNotificationManager(Handler handler) {
-            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
+        private TestableAlertingNotificationManager(Handler handler, SystemClock systemClock) {
+            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler, systemClock);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
-            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
+            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         @Override
@@ -107,7 +118,7 @@
     }
 
     protected AlertingNotificationManager createAlertingNotificationManager() {
-        return new TestableAlertingNotificationManager(mTestHandler);
+        return new TestableAlertingNotificationManager(mTestHandler, mSystemClock);
     }
 
     protected StatusBarNotification createSbn(int id, Notification n) {
@@ -167,10 +178,6 @@
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
-
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 48b95d4..37ee322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,6 +89,8 @@
                 KeyguardBypassController keyguardBypassController,
                 ConfigurationController configurationController,
                 Handler handler,
+                GlobalSettings globalSettings,
+                SystemClock systemClock,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
@@ -101,13 +105,15 @@
                     visualStabilityProvider,
                     configurationController,
                     handler,
+                    globalSettings,
+                    systemClock,
                     accessibilityManagerWrapper,
                     uiEventLogger,
                     javaAdapter,
                     shadeInteractor
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
         }
     }
 
@@ -121,6 +127,8 @@
                 mBypassController,
                 mConfigurationController,
                 mTestHandler,
+                mGlobalSettings,
+                mSystemClock,
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
                 mJavaAdapter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 4f3f564..2940c39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -34,7 +34,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -57,17 +56,25 @@
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
     private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
     private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
@@ -76,17 +83,33 @@
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
+    static {
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+                TEST_A11Y_TIMEOUT_TIME);
+    }
+
     private final class TestableHeadsUpManager extends BaseHeadsUpManager {
         TestableHeadsUpManager(Context context,
                 HeadsUpManagerLogger logger,
                 Handler handler,
+                GlobalSettings globalSettings,
+                SystemClock systemClock,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
-            super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+            super(context, logger, handler, globalSettings, systemClock,
+                    accessibilityManagerWrapper, uiEventLogger);
             mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
-            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
+            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -160,8 +183,8 @@
     }
 
     private BaseHeadsUpManager createHeadsUpManager() {
-        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
-                mUiEventLoggerFake);
+        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mGlobalSettings,
+                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
     }
 
     @Override
@@ -214,19 +237,7 @@
     @Before
     @Override
     public void setUp() {
-        initMocks(this);
         super.setUp();
-
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
-
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
-                TEST_A11Y_TIMEOUT_TIME);
     }
 
     @After
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 0327087..4642b47 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
@@ -40,9 +40,6 @@
     private val currentTime: () -> Long,
 ) : AuthenticationRepository {
 
-    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
-    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
-        _isAutoConfirmFeatureEnabled.asStateFlow()
     override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
 
     override val hintedPinLength: Int = HINTING_PIN_LENGTH
@@ -53,6 +50,12 @@
     override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
         MutableStateFlow(null)
 
+    override val hasThrottlingOccurred = MutableStateFlow(false)
+
+    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
+    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+        _isAutoConfirmFeatureEnabled.asStateFlow()
+
     private val _authenticationMethod =
         MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
     override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
@@ -107,6 +110,9 @@
 
     override suspend fun setThrottleDuration(durationMs: Int) {
         throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+        if (durationMs > 0) {
+            hasThrottlingOccurred.value = true
+        }
     }
 
     override suspend fun checkCredential(
@@ -128,6 +134,7 @@
         return if (
             isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
         ) {
+            hasThrottlingOccurred.value = false
             AuthenticationResultModel(
                 isSuccessful = isSuccessful,
                 throttleDurationMs = 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 9c10848..f7005ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,21 +18,14 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.domain.model.ShadeModel
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 
 /** Fake implementation of [ShadeRepository] */
 @SysUISingleton
 class FakeShadeRepository @Inject constructor() : ShadeRepository {
-
-    private val _shadeModel = MutableStateFlow(ShadeModel())
-    override val shadeModel: Flow<ShadeModel> = _shadeModel
-
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion = _qsExpansion
 
@@ -114,10 +107,6 @@
         _legacyIsClosing.value = isClosing
     }
 
-    fun setShadeModel(model: ShadeModel) {
-        _shadeModel.value = model
-    }
-
     override fun setQsExpansion(qsExpansion: Float) {
         _qsExpansion.value = qsExpansion
     }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 4bb9f4f..c436c72 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -127,6 +127,7 @@
         "/system/bin/mediaserver",
         "/system/bin/netd",
         "/system/bin/sdcard",
+        "/system/bin/servicemanager",
         "/system/bin/surfaceflinger",
         "/system/bin/vold",
         "media.extractor", // system/bin/mediaextractor
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 49095ce..42c2548 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1432,7 +1432,7 @@
     }
 
     private void unlockKeystore(int userId, SyntheticPassword sp) {
-        Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
+        Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index d172d3f..eac4fc0 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -865,21 +865,19 @@
             mDeviceLockedForUser.put(userId, locked);
         }
         if (changed) {
-            dispatchDeviceLocked(userId, locked);
-            Authorization.onLockScreenEvent(locked, userId, null,
-                    getBiometricSids(userId));
+            notifyTrustAgentsOfDeviceLockState(userId, locked);
+            notifyKeystoreOfDeviceLockState(userId, locked);
             // Also update the user's profiles who have unified challenge, since they
             // share the same unlocked state (see {@link #isDeviceLocked(int)})
             for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) {
                 if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(profileHandle)) {
-                    Authorization.onLockScreenEvent(locked, profileHandle, null,
-                            getBiometricSids(profileHandle));
+                    notifyKeystoreOfDeviceLockState(profileHandle, locked);
                 }
             }
         }
     }
 
-    private void dispatchDeviceLocked(int userId, boolean isLocked) {
+    private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) {
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo agent = mActiveAgents.valueAt(i);
             if (agent.userId == userId) {
@@ -892,6 +890,17 @@
         }
     }
 
+    private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
+        if (isLocked) {
+            Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+        } else {
+            // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
+            // with LSKF, this is redundant with the call from LockSettingsService which provides
+            // the password.  However, for unlocks with biometric or trust agent, this is required.
+            Authorization.onDeviceUnlocked(userId, /* password= */ null);
+        }
+    }
+
     private void dispatchEscrowTokenActivatedLocked(long handle, int userId) {
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo agent = mActiveAgents.valueAt(i);
@@ -1425,10 +1434,10 @@
         }
     }
 
-    private long[] getBiometricSids(int userId) {
+    private @NonNull long[] getBiometricSids(int userId) {
         BiometricManager biometricManager = mContext.getSystemService(BiometricManager.class);
         if (biometricManager == null) {
-            return null;
+            return new long[0];
         }
         return biometricManager.getAuthenticatorIds(userId);
     }
@@ -1680,8 +1689,7 @@
                         mDeviceLockedForUser.put(userId, locked);
                     }
 
-                    Authorization.onLockScreenEvent(locked, userId, null,
-                            getBiometricSids(userId));
+                    notifyKeystoreOfDeviceLockState(userId, locked);
 
                     if (locked) {
                         try {
diff --git a/services/core/java/com/android/server/utils/OWNERS b/services/core/java/com/android/server/utils/OWNERS
index be91611d..fbc0b56 100644
--- a/services/core/java/com/android/server/utils/OWNERS
+++ b/services/core/java/com/android/server/utils/OWNERS
@@ -10,3 +10,8 @@
 per-file Watcher.java = shombert@google.com
 per-file EventLogger.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 per-file EventLogger.java = jmtrivi@google.com
+
+# Bug component : 158088 = per-file AnrTimer*.java
+per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS
+
+per-file flags.aconfig = file:/PERFORMANCE_OWNERS
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 0e45f61..061fe0f 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -30,3 +30,6 @@
 per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
 per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
+
+# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
+per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS
index 5e24828..f5b19a1 100644
--- a/services/tests/servicestests/src/com/android/server/utils/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS
@@ -1,2 +1,5 @@
 per-file EventLoggerTest.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 per-file EventLoggerTest.java = jmtrivi@google.com
+
+# Bug component : 158088 = per-file AnrTimer*.java
+per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS