summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt372
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java82
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java174
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt173
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java4
14 files changed, 1034 insertions, 178 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ae669dcf80fa..7388c27ed313 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -254,6 +254,7 @@ public final class NotificationPanelViewController implements Dumpable {
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
+ public static final int WAKEUP_ANIMATION_DELAY_MS = 250;
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
@@ -276,6 +277,7 @@ public final class NotificationPanelViewController implements Dumpable {
private static final int NO_FIXED_DURATION = -1;
private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
+
/**
* The factor of the usual high velocity that is needed in order to reach the maximum overshoot
* when flinging. A low value will make it that most flings will reach the maximum overshoot.
@@ -588,6 +590,12 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+ /**
+ * Whether we're waking up and will play the delayed doze animation in
+ * {@link NotificationWakeUpCoordinator}. If so, we'll want to keep the clock centered until the
+ * delayed doze animation starts.
+ */
+ private boolean mWillPlayDelayedDozeAmountAnimation = false;
private final DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
@@ -1044,6 +1052,12 @@ public final class NotificationPanelViewController implements Dumpable {
requestScrollerTopPaddingUpdate(false /* animate */);
}
}
+
+ @Override
+ public void onDelayedDozeAmountAnimationRunning(boolean running) {
+ // On running OR finished, the animation is no longer waiting to play
+ setWillPlayDelayedDozeAmountAnimation(false);
+ }
});
mView.setRtlChangeListener(layoutDirection -> {
@@ -1640,11 +1654,28 @@ public final class NotificationPanelViewController implements Dumpable {
// Pulsing notification appears on the right. Move clock left to avoid overlap.
return false;
}
+ if (mWillPlayDelayedDozeAmountAnimation) {
+ return true;
+ }
// "Visible" notifications are actually not visible on AOD (unless pulsing), so it is safe
// to center the clock without overlap.
return isOnAod();
}
+ /**
+ * Notify us that {@link NotificationWakeUpCoordinator} is going to play the doze wakeup
+ * animation after a delay. If so, we'll keep the clock centered until that animation starts.
+ */
+ public void setWillPlayDelayedDozeAmountAnimation(boolean willPlay) {
+ if (mWillPlayDelayedDozeAmountAnimation == willPlay) return;
+
+ mWillPlayDelayedDozeAmountAnimation = willPlay;
+ mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay);
+
+ // Once changing this value, see if we should move the clock.
+ positionClockAndNotifications();
+ }
+
private boolean isOnAod() {
return mDozing && mDozeParameters.getAlwaysOn();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 8874f59d6c17..20af6cadeed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -16,14 +16,17 @@
package com.android.systemui.statusbar.notification
-import android.animation.ObjectAnimator
import android.util.FloatProperty
+import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
+import androidx.core.animation.ObjectAnimator
import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.InterpolatorsAndroidX
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotificationPanelViewController.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.statusbar.StatusBarState
@@ -36,12 +39,17 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassSta
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.doOnEnd
+import com.android.systemui.util.doOnStart
import java.io.PrintWriter
import javax.inject.Inject
+import kotlin.math.max
import kotlin.math.min
@SysUISingleton
-class NotificationWakeUpCoordinator @Inject constructor(
+class NotificationWakeUpCoordinator
+@Inject
+constructor(
dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
@@ -49,27 +57,25 @@ class NotificationWakeUpCoordinator @Inject constructor(
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController,
private val logger: NotificationWakeUpCoordinatorLogger,
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
+) :
+ OnHeadsUpChangedListener,
+ StatusBarStateController.StateListener,
+ ShadeExpansionListener,
Dumpable {
-
- private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
- "notificationVisibility") {
-
- override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
- coordinator.setVisibilityAmount(value)
- }
-
- override fun get(coordinator: NotificationWakeUpCoordinator): Float? {
- return coordinator.mLinearVisibilityAmount
- }
- }
private lateinit var mStackScrollerController: NotificationStackScrollLayoutController
private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
- private var mLinearDozeAmount: Float = 0.0f
- private var mDozeAmount: Float = 0.0f
- private var mDozeAmountSource: String = "init"
- private var mNotifsHiddenByDozeAmountOverride: Boolean = false
+ private var inputLinearDozeAmount: Float = 0.0f
+ private var inputEasedDozeAmount: Float = 0.0f
+ private var delayedDozeAmountOverride: Float = 0.0f
+ private var delayedDozeAmountAnimator: ObjectAnimator? = null
+ /** Valid values: {1f, 0f, null} null => use input */
+ private var hardDozeAmountOverride: Float? = null
+ private var hardDozeAmountOverrideSource: String = "n/a"
+ private var outputLinearDozeAmount: Float = 0.0f
+ private var outputEasedDozeAmount: Float = 0.0f
+ @VisibleForTesting val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN
+
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -84,27 +90,32 @@ class NotificationWakeUpCoordinator @Inject constructor(
var fullyAwake: Boolean = false
var wakingUp = false
- set(value) {
+ private set(value) {
field = value
willWakeUp = false
if (value) {
- if (mNotificationsVisible && !mNotificationsVisibleForExpansion &&
- !bypassController.bypassEnabled) {
+ if (
+ mNotificationsVisible &&
+ !mNotificationsVisibleForExpansion &&
+ !bypassController.bypassEnabled
+ ) {
// We're waking up while pulsing, let's make sure the animation looks nice
mStackScrollerController.wakeUpFromPulse()
}
if (bypassController.bypassEnabled && !mNotificationsVisible) {
// Let's make sure our huns become visible once we are waking up in case
// they were blocked by the proximity sensor
- updateNotificationVisibility(animate = shouldAnimateVisibility(),
- increaseSpeed = false)
+ updateNotificationVisibility(
+ animate = shouldAnimateVisibility(),
+ increaseSpeed = false
+ )
}
}
}
var willWakeUp = false
set(value) {
- if (!value || mDozeAmount != 0.0f) {
+ if (!value || outputLinearDozeAmount != 0.0f) {
field = value
}
}
@@ -118,8 +129,10 @@ class NotificationWakeUpCoordinator @Inject constructor(
// Only when setting pulsing to true we want an immediate update, since we get
// this already when the doze service finishes which is usually before we get
// the waking up callback
- updateNotificationVisibility(animate = shouldAnimateVisibility(),
- increaseSpeed = false)
+ updateNotificationVisibility(
+ animate = shouldAnimateVisibility(),
+ increaseSpeed = false
+ )
}
}
@@ -133,17 +146,17 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
}
- /**
- * True if we can show pulsing heads up notifications
- */
+ /** True if we can show pulsing heads up notifications */
var canShowPulsingHuns: Boolean = false
private set
get() {
var canShow = pulsing
if (bypassController.bypassEnabled) {
// We also allow pulsing on the lock screen!
- canShow = canShow || (wakingUp || willWakeUp || fullyAwake) &&
- statusBarStateController.state == StatusBarState.KEYGUARD
+ canShow =
+ canShow ||
+ (wakingUp || willWakeUp || fullyAwake) &&
+ statusBarStateController.state == StatusBarState.KEYGUARD
// We want to hide the notifications when collapsed too much
if (collapsedEnoughToHide) {
canShow = false
@@ -152,30 +165,38 @@ class NotificationWakeUpCoordinator @Inject constructor(
return canShow
}
- private val bypassStateChangedListener = object : OnBypassStateChangedListener {
- override fun onBypassStateChanged(isEnabled: Boolean) {
- // When the bypass state changes, we have to check whether we should re-show the
- // notifications by clearing the doze amount override which hides them.
- maybeClearDozeAmountOverrideHidingNotifs()
+ private val bypassStateChangedListener =
+ object : OnBypassStateChangedListener {
+ override fun onBypassStateChanged(isEnabled: Boolean) {
+ // When the bypass state changes, we have to check whether we should re-show the
+ // notifications by clearing the doze amount override which hides them.
+ maybeClearHardDozeAmountOverrideHidingNotifs()
+ }
}
- }
init {
dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
- addListener(object : WakeUpListener {
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- if (isFullyHidden && mNotificationsVisibleForExpansion) {
- // When the notification becomes fully invisible, let's make sure our expansion
- // flag also changes. This can happen if the bouncer shows when dragging down
- // and then the screen turning off, where we don't reset this state.
- setNotificationsVisibleForExpansion(visible = false, animate = false,
- increaseSpeed = false)
+ addListener(
+ object : WakeUpListener {
+ override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+ if (isFullyHidden && mNotificationsVisibleForExpansion) {
+ // When the notification becomes fully invisible, let's make sure our
+ // expansion
+ // flag also changes. This can happen if the bouncer shows when dragging
+ // down
+ // and then the screen turning off, where we don't reset this state.
+ setNotificationsVisibleForExpansion(
+ visible = false,
+ animate = false,
+ increaseSpeed = false
+ )
+ }
}
}
- })
+ )
}
fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
@@ -221,15 +242,17 @@ class NotificationWakeUpCoordinator @Inject constructor(
wakeUpListeners.remove(listener)
}
- private fun updateNotificationVisibility(
- animate: Boolean,
- increaseSpeed: Boolean
- ) {
+ private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) {
// TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore
var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications()
visible = visible && canShowPulsingHuns
- if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) && mDozeAmount != 0.0f) {
+ if (
+ !visible &&
+ mNotificationsVisible &&
+ (wakingUp || willWakeUp) &&
+ outputLinearDozeAmount != 0.0f
+ ) {
// let's not make notifications invisible while waking up, otherwise the animation
// is strange
return
@@ -257,7 +280,9 @@ class NotificationWakeUpCoordinator @Inject constructor(
override fun onDozeAmountChanged(linear: Float, eased: Float) {
logger.logOnDozeAmountChanged(linear = linear, eased = eased)
- if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
+ inputLinearDozeAmount = linear
+ inputEasedDozeAmount = eased
+ if (overrideDozeAmountIfAnimatingScreenOff()) {
return
}
@@ -265,35 +290,111 @@ class NotificationWakeUpCoordinator @Inject constructor(
return
}
- if (linear != 1.0f && linear != 0.0f &&
- (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
- // Let's notify the scroller that an animation started
- notifyAnimationStart(mLinearDozeAmount == 1.0f)
+ if (clearHardDozeAmountOverride()) {
+ return
}
- setDozeAmount(linear, eased, source = "StatusBar")
+
+ updateDozeAmount()
}
- fun setDozeAmount(
- linear: Float,
- eased: Float,
- source: String,
- hidesNotifsByOverride: Boolean = false
- ) {
- val changed = linear != mLinearDozeAmount
- logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed)
- mLinearDozeAmount = linear
- mDozeAmount = eased
- mDozeAmountSource = source
- mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride
- mStackScrollerController.setDozeAmount(mDozeAmount)
+ private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
+ logger.logSetDozeAmountOverride(dozing = dozing, source = source)
+ hardDozeAmountOverride = if (dozing) 1f else 0f
+ hardDozeAmountOverrideSource = source
+ updateDozeAmount()
+ }
+
+ private fun clearHardDozeAmountOverride(): Boolean {
+ if (hardDozeAmountOverride == null) return false
+ hardDozeAmountOverride = null
+ hardDozeAmountOverrideSource = "Cleared: $hardDozeAmountOverrideSource"
+ updateDozeAmount()
+ return true
+ }
+
+ private fun updateDozeAmount() {
+ // Calculate new doze amount (linear)
+ val newOutputLinearDozeAmount =
+ hardDozeAmountOverride ?: max(inputLinearDozeAmount, delayedDozeAmountOverride)
+ val changed = outputLinearDozeAmount != newOutputLinearDozeAmount
+
+ // notify when the animation is starting
+ if (
+ newOutputLinearDozeAmount != 1.0f &&
+ newOutputLinearDozeAmount != 0.0f &&
+ (outputLinearDozeAmount == 0.0f || outputLinearDozeAmount == 1.0f)
+ ) {
+ // Let's notify the scroller that an animation started
+ notifyAnimationStart(outputLinearDozeAmount == 1.0f)
+ }
+
+ // Update output doze amount
+ outputLinearDozeAmount = newOutputLinearDozeAmount
+ outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount)
+ logger.logUpdateDozeAmount(
+ inputLinear = inputLinearDozeAmount,
+ delayLinear = delayedDozeAmountOverride,
+ hardOverride = hardDozeAmountOverride,
+ outputLinear = outputLinearDozeAmount,
+ state = statusBarStateController.state,
+ changed = changed
+ )
+ mStackScrollerController.setDozeAmount(outputEasedDozeAmount)
updateHideAmount()
- if (changed && linear == 0.0f) {
+ if (changed && outputLinearDozeAmount == 0.0f) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
- setNotificationsVisibleForExpansion(visible = false, animate = false,
- increaseSpeed = false)
+ setNotificationsVisibleForExpansion(
+ visible = false,
+ animate = false,
+ increaseSpeed = false
+ )
+ }
+ }
+
+ /**
+ * Notifies the wakeup coordinator that we're waking up.
+ *
+ * [requestDelayedAnimation] is used to request that we delay the start of the wakeup animation
+ * in order to wait for a potential fingerprint authentication to arrive, since unlocking during
+ * the wakeup animation looks chaotic.
+ *
+ * If called with [wakingUp] and [requestDelayedAnimation] both `true`, the [WakeUpListener]s
+ * are guaranteed to receive at least one [WakeUpListener.onDelayedDozeAmountAnimationRunning]
+ * call with `false` at some point in the near future. A call with `true` before that will
+ * happen if the animation is not already running.
+ */
+ fun setWakingUp(
+ wakingUp: Boolean,
+ requestDelayedAnimation: Boolean,
+ ) {
+ logger.logSetWakingUp(wakingUp, requestDelayedAnimation)
+ this.wakingUp = wakingUp
+ if (wakingUp && requestDelayedAnimation) {
+ scheduleDelayedDozeAmountAnimation()
}
}
+ private fun scheduleDelayedDozeAmountAnimation() {
+ val alreadyRunning = delayedDozeAmountAnimator != null
+ logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
+ if (alreadyRunning) return
+ delayedDozeAmount.setValue(this, 1.0f)
+ delayedDozeAmountAnimator =
+ ObjectAnimator.ofFloat(this, delayedDozeAmount, 0.0f).apply {
+ interpolator = InterpolatorsAndroidX.LINEAR
+ duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
+ startDelay = WAKEUP_ANIMATION_DELAY_MS.toLong()
+ doOnStart {
+ wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(true) }
+ }
+ doOnEnd {
+ delayedDozeAmountAnimator = null
+ wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(false) }
+ }
+ start()
+ }
+ }
+
override fun onStateChanged(newState: Int) {
logger.logOnStateChanged(newState = newState, storedState = state)
if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
@@ -302,12 +403,15 @@ class NotificationWakeUpCoordinator @Inject constructor(
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
+ setHardDozeAmountOverride(
+ dozing = false,
+ source = "Override: Shade->Shade (lock cancelled by unlock)"
+ )
this.state = newState
return
}
- if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
+ if (overrideDozeAmountIfAnimatingScreenOff()) {
this.state = newState
return
}
@@ -317,7 +421,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
return
}
- maybeClearDozeAmountOverrideHidingNotifs()
+ maybeClearHardDozeAmountOverrideHidingNotifs()
this.state = newState
}
@@ -340,15 +444,14 @@ class NotificationWakeUpCoordinator @Inject constructor(
/**
* @return Whether the doze amount was overridden because bypass is enabled. If true, the
- * original doze amount should be ignored.
+ * original doze amount should be ignored.
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
if (statusBarStateController.state == StatusBarState.KEYGUARD) {
- setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)",
- hidesNotifsByOverride = true)
+ setHardDozeAmountOverride(dozing = true, source = "Override: bypass (keyguard)")
} else {
- setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
+ setHardDozeAmountOverride(dozing = false, source = "Override: bypass (shade)")
}
return true
}
@@ -362,26 +465,28 @@ class NotificationWakeUpCoordinator @Inject constructor(
* This fixes bugs where the bypass state changing could result in stale overrides, hiding
* notifications either on the inside screen or even after unlock.
*/
- private fun maybeClearDozeAmountOverrideHidingNotifs() {
- if (mNotifsHiddenByDozeAmountOverride) {
+ private fun maybeClearHardDozeAmountOverrideHidingNotifs() {
+ if (hardDozeAmountOverride == 1f) {
val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
val dozing = statusBarStateController.isDozing
val bypass = bypassController.bypassEnabled
val animating =
- screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
+ screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
// Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
// [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
// clear the override if both those conditions are cleared. But also require either
// !dozing or !onKeyguard because those conditions should indicate that we intend
// notifications to be visible, and thus it is safe to unhide them.
val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
- logger.logMaybeClearDozeAmountOverrideHidingNotifs(
- willRemove = willRemove,
- onKeyguard = onKeyguard, dozing = dozing,
- bypass = bypass, animating = animating,
+ logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
+ willRemove = willRemove,
+ onKeyguard = onKeyguard,
+ dozing = dozing,
+ bypass = bypass,
+ animating = animating,
)
if (willRemove) {
- setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource")
+ clearHardDozeAmountOverride()
}
}
}
@@ -392,12 +497,11 @@ class NotificationWakeUpCoordinator @Inject constructor(
* off and dozeAmount goes from 1f to 0f.
*
* @return Whether the doze amount was overridden because we are playing the screen off
- * animation. If true, the original doze amount should be ignored.
+ * animation. If true, the original doze amount should be ignored.
*/
- private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
+ private fun overrideDozeAmountIfAnimatingScreenOff(): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f, source = "Override: animating screen off",
- hidesNotifsByOverride = true)
+ setHardDozeAmountOverride(dozing = true, source = "Override: animating screen off")
return true
}
@@ -406,41 +510,41 @@ class NotificationWakeUpCoordinator @Inject constructor(
private fun startVisibilityAnimation(increaseSpeed: Boolean) {
if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) {
- mVisibilityInterpolator = if (mNotificationsVisible)
- Interpolators.TOUCH_RESPONSE
- else
- Interpolators.FAST_OUT_SLOW_IN_REVERSE
+ mVisibilityInterpolator =
+ if (mNotificationsVisible) Interpolators.TOUCH_RESPONSE
+ else Interpolators.FAST_OUT_SLOW_IN_REVERSE
}
val target = if (mNotificationsVisible) 1.0f else 0.0f
- val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
- visibilityAnimator.setInterpolator(Interpolators.LINEAR)
+ val visibilityAnimator = ObjectAnimator.ofFloat(this, notificationVisibility, target)
+ visibilityAnimator.interpolator = InterpolatorsAndroidX.LINEAR
var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
if (increaseSpeed) {
duration = (duration.toFloat() / 1.5F).toLong()
}
- visibilityAnimator.setDuration(duration)
+ visibilityAnimator.duration = duration
visibilityAnimator.start()
mVisibilityAnimator = visibilityAnimator
}
private fun setVisibilityAmount(visibilityAmount: Float) {
+ logger.logSetVisibilityAmount(visibilityAmount)
mLinearVisibilityAmount = visibilityAmount
- mVisibilityAmount = mVisibilityInterpolator.getInterpolation(
- visibilityAmount)
+ mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount)
handleAnimationFinished()
updateHideAmount()
}
private fun handleAnimationFinished() {
- if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
+ if (outputLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) }
mEntrySetToClearWhenFinished.clear()
}
}
private fun updateHideAmount() {
- val linearAmount = min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
- val amount = min(1.0f - mVisibilityAmount, mDozeAmount)
+ val linearAmount = min(1.0f - mLinearVisibilityAmount, outputLinearDozeAmount)
+ val amount = min(1.0f - mVisibilityAmount, outputEasedDozeAmount)
+ logger.logSetHideAmount(linearAmount)
mStackScrollerController.setHideAmount(linearAmount, amount)
notificationsFullyHidden = linearAmount == 1.0f
}
@@ -458,7 +562,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
var animate = shouldAnimateVisibility()
if (!isHeadsUp) {
- if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
+ if (outputLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
if (entry.isRowDismissed) {
// if we animate, we see the shelf briefly visible. Instead we fully animate
// the notification and its background out
@@ -477,13 +581,16 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
private fun shouldAnimateVisibility() =
- dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("mLinearDozeAmount: $mLinearDozeAmount")
- pw.println("mDozeAmount: $mDozeAmount")
- pw.println("mDozeAmountSource: $mDozeAmountSource")
- pw.println("mNotifsHiddenByDozeAmountOverride: $mNotifsHiddenByDozeAmountOverride")
+ pw.println("inputLinearDozeAmount: $inputLinearDozeAmount")
+ pw.println("inputEasedDozeAmount: $inputEasedDozeAmount")
+ pw.println("delayedDozeAmountOverride: $delayedDozeAmountOverride")
+ pw.println("hardDozeAmountOverride: $hardDozeAmountOverride")
+ pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource")
+ pw.println("outputLinearDozeAmount: $outputLinearDozeAmount")
+ pw.println("outputEasedDozeAmount: $outputEasedDozeAmount")
pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
pw.println("mNotificationsVisible: $mNotificationsVisible")
pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
@@ -500,16 +607,53 @@ class NotificationWakeUpCoordinator @Inject constructor(
pw.println("canShowPulsingHuns: $canShowPulsingHuns")
}
+ fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) {
+ logger.logDelayingClockWakeUpAnimation(delayingAnimation)
+ }
+
interface WakeUpListener {
- /**
- * Called whenever the notifications are fully hidden or shown
- */
+ /** Called whenever the notifications are fully hidden or shown */
@JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
/**
* Called whenever the pulseExpansion changes
+ *
* @param expandingChanged if the user has started or stopped expanding
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+
+ /**
+ * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
+ * after the start delay, or after it ends/is cancelled.
+ */
+ @JvmDefault fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+ }
+
+ companion object {
+ private val notificationVisibility =
+ object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") {
+
+ override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
+ coordinator.setVisibilityAmount(value)
+ }
+
+ override fun get(coordinator: NotificationWakeUpCoordinator): Float {
+ return coordinator.mLinearVisibilityAmount
+ }
+ }
+
+ private val delayedDozeAmount =
+ object : FloatProperty<NotificationWakeUpCoordinator>("delayedDozeAmount") {
+
+ override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
+ coordinator.delayedDozeAmountOverride = value
+ coordinator.logger.logSetDelayDozeAmountOverride(value)
+ coordinator.updateDozeAmount()
+ }
+
+ override fun get(coordinator: NotificationWakeUpCoordinator): Float {
+ return coordinator.delayedDozeAmountOverride
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 88d9ffcdcf3e..dd3c2a9df3e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -22,50 +22,75 @@ import javax.inject.Inject
class NotificationWakeUpCoordinatorLogger
@Inject
constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
- private var lastSetDozeAmountLogWasFractional = false
+ private var allowThrottle = true
+ private var lastSetDozeAmountLogInputWasFractional = false
+ private var lastSetDozeAmountLogDelayWasFractional = false
private var lastSetDozeAmountLogState = -1
- private var lastSetDozeAmountLogSource = "undefined"
+ private var lastSetHardOverride: Float? = null
private var lastOnDozeAmountChangedLogWasFractional = false
+ private var lastSetDelayDozeAmountOverrideLogWasFractional = false
+ private var lastSetVisibilityAmountLogWasFractional = false
+ private var lastSetHideAmountLogWasFractional = false
+ private var lastSetHideAmount = -1f
- fun logSetDozeAmount(
- linear: Float,
- eased: Float,
- source: String,
+ fun logUpdateDozeAmount(
+ inputLinear: Float,
+ delayLinear: Float,
+ hardOverride: Float?,
+ outputLinear: Float,
state: Int,
changed: Boolean,
) {
// Avoid logging on every frame of the animation if important values are not changing
- val isFractional = linear != 1f && linear != 0f
+ val isInputFractional = inputLinear != 1f && inputLinear != 0f
+ val isDelayFractional = delayLinear != 1f && delayLinear != 0f
if (
- lastSetDozeAmountLogWasFractional &&
- isFractional &&
+ (isInputFractional || isDelayFractional) &&
+ lastSetDozeAmountLogInputWasFractional == isInputFractional &&
+ lastSetDozeAmountLogDelayWasFractional == isDelayFractional &&
lastSetDozeAmountLogState == state &&
- lastSetDozeAmountLogSource == source
+ lastSetHardOverride == hardOverride &&
+ allowThrottle
) {
return
}
- lastSetDozeAmountLogWasFractional = isFractional
+ lastSetDozeAmountLogInputWasFractional = isInputFractional
+ lastSetDozeAmountLogDelayWasFractional = isDelayFractional
lastSetDozeAmountLogState = state
- lastSetDozeAmountLogSource = source
+ lastSetHardOverride = hardOverride
buffer.log(
TAG,
DEBUG,
{
- double1 = linear.toDouble()
- str2 = eased.toString()
- str3 = source
+ double1 = inputLinear.toDouble()
+ str1 = hardOverride.toString()
+ str2 = outputLinear.toString()
+ str3 = delayLinear.toString()
int1 = state
bool1 = changed
},
{
- "setDozeAmount(linear=$double1, eased=$str2, source=$str3)" +
+ "updateDozeAmount() inputLinear=$double1 delayLinear=$str3" +
+ " hardOverride=$str1 outputLinear=$str2" +
" state=${StatusBarState.toString(int1)} changed=$bool1"
}
)
}
- fun logMaybeClearDozeAmountOverrideHidingNotifs(
+ fun logSetDozeAmountOverride(dozing: Boolean, source: String) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = dozing
+ str1 = source
+ },
+ { "setDozeAmountOverride(dozing=$bool1, source=\"$str1\")" }
+ )
+ }
+
+ fun logMaybeClearHardDozeAmountOverrideHidingNotifs(
willRemove: Boolean,
onKeyguard: Boolean,
dozing: Boolean,
@@ -80,14 +105,14 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
"willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
" bypass=$bypass animating=$animating"
},
- { "maybeClearDozeAmountOverrideHidingNotifs() $str1" }
+ { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
)
}
fun logOnDozeAmountChanged(linear: Float, eased: Float) {
// Avoid logging on every frame of the animation when values are fractional
val isFractional = linear != 1f && linear != 0f
- if (lastOnDozeAmountChangedLogWasFractional && isFractional) return
+ if (lastOnDozeAmountChangedLogWasFractional && isFractional && allowThrottle) return
lastOnDozeAmountChangedLogWasFractional = isFractional
buffer.log(
TAG,
@@ -100,6 +125,47 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
)
}
+ fun logSetDelayDozeAmountOverride(linear: Float) {
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetDelayDozeAmountOverrideLogWasFractional && isFractional && allowThrottle) return
+ lastSetDelayDozeAmountOverrideLogWasFractional = isFractional
+ buffer.log(
+ TAG,
+ DEBUG,
+ { double1 = linear.toDouble() },
+ { "setDelayDozeAmountOverride($double1)" }
+ )
+ }
+
+ fun logSetVisibilityAmount(linear: Float) {
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetVisibilityAmountLogWasFractional && isFractional && allowThrottle) return
+ lastSetVisibilityAmountLogWasFractional = isFractional
+ buffer.log(TAG, DEBUG, { double1 = linear.toDouble() }, { "setVisibilityAmount($double1)" })
+ }
+
+ fun logSetHideAmount(linear: Float) {
+ // Avoid logging the same value repeatedly
+ if (lastSetHideAmount == linear && allowThrottle) return
+ lastSetHideAmount = linear
+ // Avoid logging on every frame of the animation when values are fractional
+ val isFractional = linear != 1f && linear != 0f
+ if (lastSetHideAmountLogWasFractional && isFractional && allowThrottle) return
+ lastSetHideAmountLogWasFractional = isFractional
+ buffer.log(TAG, DEBUG, { double1 = linear.toDouble() }, { "setHideAmount($double1)" })
+ }
+
+ fun logStartDelayedDozeAmountAnimation(alreadyRunning: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = alreadyRunning },
+ { "startDelayedDozeAmountAnimation() alreadyRunning=$bool1" }
+ )
+ }
+
fun logOnStateChanged(newState: Int, storedState: Int) {
buffer.log(
TAG,
@@ -114,6 +180,27 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
}
)
}
+
+ fun logSetWakingUp(wakingUp: Boolean, requestDelayedAnimation: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = wakingUp
+ bool2 = requestDelayedAnimation
+ },
+ { "setWakingUp(wakingUp=$bool1, requestDelayedAnimation=$bool2)" }
+ )
+ }
+
+ fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = delayingAnimation },
+ { "logDelayingClockWakeUpAnimation($bool1)" }
+ )
+ }
}
private const val TAG = "NotificationWakeUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2c088fa66a8a..300587627c18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5565,6 +5565,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
updateContinuousBackgroundDrawing();
+ updateStackPosition();
requestChildrenUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 311728fff6aa..b62450b22889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -53,6 +53,7 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.util.Compile;
import java.io.PrintWriter;
@@ -71,6 +72,7 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
boolean DEBUG_CAMERA_LIFT = false;
boolean DEBUG_WINDOW_STATE = false;
+ boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG;
// additional instrumentation for testing purposes; intended to be left on during development
boolean CHATTY = DEBUG;
boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
@@ -536,6 +538,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void extendDozePulse();
+ boolean shouldDelayWakeUpAnimation();
+
public static class KeyboardShortcutsMessage {
final int mDeviceId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index de129a3a2eb4..6cc99beecd03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -69,6 +69,7 @@ import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
@@ -257,6 +258,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
import dagger.Lazy;
@@ -446,10 +448,20 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
DozeServiceHost mDozeServiceHost;
- private boolean mWakeUpComingFromTouch;
private LightRevealScrim mLightRevealScrim;
private PowerButtonReveal mPowerButtonReveal;
+ private boolean mWakeUpComingFromTouch;
+
+ /**
+ * Whether we should delay the wakeup animation (which shows the notifications and moves the
+ * clock view). This is typically done when waking up from a 'press to unlock' gesture on a
+ * device with a side fingerprint sensor, so that if the fingerprint scan is successful, we
+ * can play the unlock animation directly rather than interrupting the wakeup animation part
+ * way through.
+ */
+ private boolean mShouldDelayWakeUpAnimation = false;
+
private final Object mQueueLock = new Object();
private final PulseExpansionHandler mPulseExpansionHandler;
@@ -510,6 +522,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final MessageRouter mMessageRouter;
private final WallpaperManager mWallpaperManager;
private final UserTracker mUserTracker;
+ private final Provider<FingerprintManager> mFingerprintManager;
private CentralSurfacesComponent mCentralSurfacesComponent;
@@ -753,7 +766,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
Lazy<CameraLauncher> cameraLauncherLazy,
Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
AlternateBouncerInteractor alternateBouncerInteractor,
- UserTracker userTracker
+ UserTracker userTracker,
+ Provider<FingerprintManager> fingerprintManager
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -834,6 +848,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mCameraLauncherLazy = cameraLauncherLazy;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUserTracker = userTracker;
+ mFingerprintManager = fingerprintManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -3137,6 +3152,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
+ public boolean shouldDelayWakeUpAnimation() {
+ return mShouldDelayWakeUpAnimation;
+ }
+
private void updateDozingState() {
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("CentralSurfaces#updateDozingState");
@@ -3147,11 +3166,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
boolean keyguardVisibleOrWillBe =
keyguardVisible || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
- boolean wakeAndUnlock = mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
- boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
- || (mDozing && mDozeParameters.shouldControlScreenOff()
- && keyguardVisibleOrWillBe);
+ boolean animate = (!mDozing && shouldAnimateDozeWakeup())
+ || (mDozing && mDozeParameters.shouldControlScreenOff() && keyguardVisibleOrWillBe);
mNotificationPanelViewController.setDozing(mDozing, animate);
updateQsExpansionEnabled();
@@ -3504,7 +3520,44 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
DejankUtils.startDetectingBlockingIpcs(tag);
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- mWakeUpCoordinator.setWakingUp(true);
+
+ if (shouldAnimateDozeWakeup()) {
+ // If this is false, the power button must be physically pressed in order to
+ // trigger fingerprint authentication.
+ final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ -1,
+ mUserTracker.getUserId()) > 0;
+
+ // Delay if we're waking up, not mid-doze animation (which means we are
+ // cancelling a sleep), from the power button, on a device with a power button
+ // FPS, and 'press to unlock' is required.
+ mShouldDelayWakeUpAnimation =
+ !isPulsing()
+ && mStatusBarStateController.getDozeAmount() == 1f
+ && mWakefulnessLifecycle.getLastWakeReason()
+ == PowerManager.WAKE_REASON_POWER_BUTTON
+ && mFingerprintManager.get().isPowerbuttonFps()
+ && mFingerprintManager.get().hasEnrolledFingerprints()
+ && !touchToUnlockAnytime;
+ if (DEBUG_WAKEUP_DELAY) {
+ Log.d(TAG, "mShouldDelayWakeUpAnimation=" + mShouldDelayWakeUpAnimation);
+ }
+ } else {
+ // If we're not animating anyway, we do not need to delay it.
+ mShouldDelayWakeUpAnimation = false;
+ if (DEBUG_WAKEUP_DELAY) {
+ Log.d(TAG, "mShouldDelayWakeUpAnimation CLEARED");
+ }
+ }
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(
+ mShouldDelayWakeUpAnimation);
+ mWakeUpCoordinator.setWakingUp(
+ /* wakingUp= */ true,
+ mShouldDelayWakeUpAnimation);
+
if (!mKeyguardBypassController.getBypassEnabled()) {
mHeadsUpManager.releaseAllImmediately();
}
@@ -3531,7 +3584,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
- mWakeUpCoordinator.setWakingUp(false);
+ mWakeUpCoordinator.setWakingUp(false, false);
if (mKeyguardStateController.isOccluded()
&& !mDozeParameters.canControlUnlockedScreenOff()) {
// When the keyguard is occluded we don't use the KEYGUARD state which would
@@ -4395,4 +4448,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
return mUserTracker.getUserHandle();
}
+
+ /**
+ * Whether we want to animate the wake animation AOD to lockscreen. This is done only if the
+ * doze service host says we can, and also we're not wake and unlocking (in which case the
+ * AOD instantly hides).
+ */
+ private boolean shouldAnimateDozeWakeup() {
+ return mDozeServiceHost.shouldAnimateWakeup()
+ && mBiometricUnlockController.getMode()
+ != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ }
}
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
new file mode 100644
index 000000000000..e93e86291535
--- /dev/null
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.animation;
+
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.AndroidRuntimeException;
+
+import androidx.annotation.NonNull;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
+ * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
+ * list of callbacks.
+ *
+ * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
+ */
+public final class AnimatorTestRule2 implements TestRule {
+
+ class TestAnimationHandler extends AnimationHandler {
+ TestAnimationHandler() {
+ super(new TestProvider());
+ }
+
+ List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
+
+ @Override
+ void addAnimationFrameCallback(AnimationFrameCallback callback) {
+ animationCallbacks.add(callback);
+ callback.doAnimationFrame(getCurrentTime());
+ }
+
+ @Override
+ public void removeCallback(AnimationFrameCallback callback) {
+ int id = animationCallbacks.indexOf(callback);
+ if (id >= 0) {
+ animationCallbacks.set(id, null);
+ }
+ }
+
+ void onAnimationFrame(long frameTime) {
+ for (int i = 0; i < animationCallbacks.size(); i++) {
+ final AnimationFrameCallback callback = animationCallbacks.get(i);
+ if (callback == null) {
+ continue;
+ }
+ callback.doAnimationFrame(frameTime);
+ }
+ }
+
+ @Override
+ void autoCancelBasedOn(ObjectAnimator objectAnimator) {
+ for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
+ AnimationFrameCallback cb = animationCallbacks.get(i);
+ if (cb == null) {
+ continue;
+ }
+ if (objectAnimator.shouldAutoCancel(cb)) {
+ ((Animator) animationCallbacks.get(i)).cancel();
+ }
+ }
+ }
+ }
+
+ final TestAnimationHandler mTestHandler;
+ final long mStartTime;
+ private long mTotalTimeDelta = 0;
+ private final Object mLock = new Object();
+
+ public AnimatorTestRule2() {
+ mStartTime = SystemClock.uptimeMillis();
+ mTestHandler = new TestAnimationHandler();
+ }
+
+ @NonNull
+ @Override
+ public Statement apply(@NonNull final Statement base, @NonNull Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ AnimationHandler.setTestHandler(mTestHandler);
+ try {
+ base.evaluate();
+ } finally {
+ AnimationHandler.setTestHandler(null);
+ }
+ }
+ };
+ }
+
+ /**
+ * Advances the animation clock by the given amount of delta in milliseconds. This call will
+ * produce an animation frame to all the ongoing animations. This method needs to be
+ * called on the same thread as {@link Animator#start()}.
+ *
+ * @param timeDelta the amount of milliseconds to advance
+ */
+ public void advanceTimeBy(long timeDelta) {
+ if (Looper.myLooper() == null) {
+ // Throw an exception
+ throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
+ + "called on Looper threads");
+ }
+ synchronized (mLock) {
+ // Advance time & pulse a frame
+ mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
+ }
+ // produce a frame
+ mTestHandler.onAnimationFrame(getCurrentTime());
+ }
+
+
+ /**
+ * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
+ * different time than the time tracked by {@link SystemClock} This method needs to be called on
+ * the same thread as {@link Animator#start()}.
+ */
+ public long getCurrentTime() {
+ if (Looper.myLooper() == null) {
+ // Throw an exception
+ throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
+ + "called on Looper threads");
+ }
+ synchronized (mLock) {
+ return mStartTime + mTotalTimeDelta;
+ }
+ }
+
+
+ private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
+ TestProvider() {
+ }
+
+ @Override
+ public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
+ callback.doAnimationFrame(getCurrentTime());
+ }
+
+ @Override
+ public void postFrameCallback() {
+ }
+
+ @Override
+ public void setFrameDelay(long delay) {
+ }
+
+ @Override
+ public long getFrameDelay() {
+ return 0;
+ }
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
new file mode 100644
index 000000000000..bddd60b5970a
--- /dev/null
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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 androidx.core.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.doOnEnd
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+class AnimatorTestRuleTest : SysuiTestCase() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
+
+ @Test
+ fun testA() {
+ didTouchA = false
+ didTouchB = false
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ animatorTestRule.advanceTimeBy(100)
+ assertThat(didTouchA).isTrue()
+ assertThat(didTouchB).isFalse()
+ }
+
+ @Test
+ fun testB() {
+ didTouchA = false
+ didTouchB = false
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ animatorTestRule.advanceTimeBy(100)
+ assertThat(didTouchA).isFalse()
+ assertThat(didTouchB).isTrue()
+ }
+
+ companion object {
+ var didTouchA = false
+ var didTouchB = false
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index b868018b7dda..be0d933e2c5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -437,6 +437,34 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+ assertKeyguardStatusViewCentered();
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
+ assertKeyguardStatusViewNotCentered();
+ }
+
+ @Test
+ public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+ assertKeyguardStatusViewCentered();
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
+ assertKeyguardStatusViewCentered();
+ }
+
+ @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 08a9f3139d71..7b59cc284181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
+import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
private val fakeFeatureFlags = FakeFeatureFlags()
- @get:Rule val animatorTestRule = AnimatorTestRule()
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index 7a6779684fc5..bef9fcb5697c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogcatEchoTracker
@@ -45,47 +45,130 @@ class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
}
@Test
- fun setDozeAmountWillThrottleFractionalUpdates() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateVisibilityThrottleFractionalUpdates() {
+ logger.logSetVisibilityAmount(0f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(0.1f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(0.2f)
+ logger.logSetVisibilityAmount(0.3f)
+ logger.logSetVisibilityAmount(0.4f)
+ logger.logSetVisibilityAmount(0.5f)
verifyDidLog(0)
- logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetVisibilityAmount(1f)
verifyDidLog(1)
}
@Test
- fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateHideAmountThrottleFractionalOrRepeatedUpdates() {
+ logger.logSetHideAmount(0f)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logSetHideAmount(0f)
+ logger.logSetHideAmount(0f)
+ verifyDidLog(0)
+ logger.logSetHideAmount(0.1f)
+ verifyDidLog(1)
+ logger.logSetHideAmount(0.2f)
+ logger.logSetHideAmount(0.3f)
+ logger.logSetHideAmount(0.4f)
+ logger.logSetHideAmount(0.5f)
+ logger.logSetHideAmount(0.5f)
+ logger.logSetHideAmount(0.5f)
+ verifyDidLog(0)
+ logger.logSetHideAmount(1f)
+ verifyDidLog(1)
+ logger.logSetHideAmount(1f)
+ logger.logSetHideAmount(1f)
+ verifyDidLog(0)
+ }
+
+ @Test
+ fun updateDozeAmountWillThrottleFractionalInputUpdates() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(1f, 0f, null, 1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ }
+
+ @Test
+ fun updateDozeAmountWillThrottleFractionalDelayUpdates() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0f, 0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0f, 0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0f, 0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0f, 1f, null, 1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ }
+
+ @Test
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenOtherInputChangesFractionality() {
+ logger.logUpdateDozeAmount(0.0f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.3f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.4f, 1.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0.5f, 0.9f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.6f, 0.8f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.8f, 0.6f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.9f, 0.5f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(1.0f, 0.4f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(1.0f, 0.3f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.2f, 1f, 1f, StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.1f, 1f, 1f, StatusBarState.SHADE, changed = false)
verifyDidLog(0)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false)
+ logger.logUpdateDozeAmount(1.0f, 0.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
}
@Test
- fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() {
- logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
- logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
- logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
- logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
verifyDidLog(0)
- logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.KEYGUARD, changed = false)
+ verifyDidLog(1)
+ }
+
+ @Test
+ fun updateDozeAmountWillIncludeFractionalUpdatesWhenHardOverrideChanges() {
+ logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.2f, 0f, null, 0.2f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.3f, 0f, null, 0.3f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.4f, 0f, null, 0.4f, StatusBarState.SHADE, changed = true)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(0)
+ logger.logUpdateDozeAmount(0.5f, 0f, 1f, 1f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.5f, 0f, 0f, 0f, StatusBarState.SHADE, changed = true)
+ verifyDidLog(1)
+ logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 95591a4b321c..be3b7234a1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -17,31 +17,42 @@
package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotificationPanelViewController.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
+ @get:Rule val animatorTestRule = AnimatorTestRule2()
+
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -50,6 +61,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
private val screenOffAnimationController: ScreenOffAnimationController = mock()
private val logger: NotificationWakeUpCoordinatorLogger = mock()
private val stackScrollerController: NotificationStackScrollLayoutController = mock()
+ private val wakeUpListener: NotificationWakeUpCoordinator.WakeUpListener = mock()
private lateinit var notificationWakeUpCoordinator: NotificationWakeUpCoordinator
private lateinit var statusBarStateCallback: StatusBarStateController.StateListener
@@ -57,7 +69,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
private var bypassEnabled: Boolean = false
private var statusBarState: Int = StatusBarState.KEYGUARD
- private var dozeAmount: Float = 0f
+ private fun eased(dozeAmount: Float) =
+ notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)
private fun setBypassEnabled(enabled: Boolean) {
bypassEnabled = enabled
@@ -70,7 +83,6 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
}
private fun setDozeAmount(dozeAmount: Float) {
- this.dozeAmount = dozeAmount
statusBarStateCallback.onDozeAmountChanged(dozeAmount, dozeAmount)
}
@@ -129,7 +141,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
fun setDozeToZeroWithBypassWillFullyHideNotifications() {
bypassEnabled = true
setDozeAmount(0f)
- verifyStackScrollerDozeAndHideAmount(dozeAmount = 01f, hideAmount = 1f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
}
@@ -152,12 +164,161 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
assertThat(notificationWakeUpCoordinator.statusBarState).isEqualTo(StatusBarState.SHADE)
}
+ private val delayedDozeDelay = WAKEUP_ANIMATION_DELAY_MS.toLong()
+ private val delayedDozeDuration = ANIMATION_DURATION_WAKEUP.toLong()
+
+ @Test
+ fun dozeAmountOutputClampsTo1WhenDelayStarts() {
+ notificationWakeUpCoordinator.setWakingUp(true, requestDelayedAnimation = true)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // verify further doze amount changes have no effect on output
+ setDozeAmount(0.5f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+ }
+
+ @Test
+ fun verifyDozeAmountOutputTracksDelay() {
+ dozeAmountOutputClampsTo1WhenDelayStarts()
+
+ // Animator waiting the delay amount should not yet affect the output
+ animatorTestRule.advanceTimeBy(delayedDozeDelay)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // Advancing the delay to 50% will cause the 50% output
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Now advance delay to 100% completion; notifications become fully visible
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Now advance delay to 200% completion -- should not invoke anything else
+ animatorTestRule.advanceTimeBy(delayedDozeDuration)
+ verify(stackScrollerController, never()).setDozeAmount(anyFloat())
+ verify(stackScrollerController, never()).setHideAmount(anyFloat(), anyFloat())
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+ }
+
+ @Test
+ fun verifyWakeUpListenerCallbacksWhenDozing() {
+ // prime internal state as dozing, then add the listener
+ setDozeAmount(1f)
+ notificationWakeUpCoordinator.addListener(wakeUpListener)
+
+ setDozeAmount(0.5f)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ setDozeAmount(0f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ setDozeAmount(0.5f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ setDozeAmount(1f)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(true))
+ verifyNoMoreInteractions(wakeUpListener)
+ }
+
+ @Test
+ fun verifyWakeUpListenerCallbacksWhenDelayingAnimation() {
+ // prime internal state as dozing, then add the listener
+ setDozeAmount(1f)
+ notificationWakeUpCoordinator.addListener(wakeUpListener)
+
+ // setWakingUp() doesn't do anything yet
+ notificationWakeUpCoordinator.setWakingUp(true, requestDelayedAnimation = true)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // verify further doze amount changes have no effect
+ setDozeAmount(0.5f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // advancing to just before the start time should not invoke the listener
+ animatorTestRule.advanceTimeBy(delayedDozeDelay - 1)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ animatorTestRule.advanceTimeBy(1)
+ verify(wakeUpListener).onDelayedDozeAmountAnimationRunning(eq(true))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // Advancing the delay to 50% will cause notifications to no longer be fully hidden
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2)
+ verify(wakeUpListener).onFullyHiddenChanged(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // Now advance delay to 99.x% completion; notifications become fully visible
+ animatorTestRule.advanceTimeBy(delayedDozeDuration / 2 - 1)
+ verifyNoMoreInteractions(wakeUpListener)
+
+ // advance to 100%; animation no longer running
+ animatorTestRule.advanceTimeBy(1)
+ verify(wakeUpListener).onDelayedDozeAmountAnimationRunning(eq(false))
+ verifyNoMoreInteractions(wakeUpListener)
+ clearInvocations(wakeUpListener)
+
+ // Now advance delay to 200% completion -- should not invoke anything else
+ animatorTestRule.advanceTimeBy(delayedDozeDuration)
+ verifyNoMoreInteractions(wakeUpListener)
+ }
+
+ @Test
+ fun verifyDelayedDozeAmountCanBeOverridden() {
+ dozeAmountOutputClampsTo1WhenDelayStarts()
+
+ // input doze amount change to 0 has no effect
+ setDozeAmount(0.0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ // Advancing the delay to 50% will cause the 50% output
+ animatorTestRule.advanceTimeBy(delayedDozeDelay + delayedDozeDuration / 2)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+
+ // Enabling bypass and showing keyguard will override back to fully dozing/hidden
+ setBypassEnabled(true)
+ setStatusBarState(StatusBarState.KEYGUARD)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+ }
+
+ @Test
+ fun verifyRemovingOverrideRestoresOtherwiseCalculatedDozeAmount() {
+ verifyDelayedDozeAmountCanBeOverridden()
+
+ // Disabling bypass will return back to the 50% value
+ setBypassEnabled(false)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+ }
+
private fun verifyStackScrollerDozeAndHideAmount(dozeAmount: Float, hideAmount: Float) {
// First verify that we did in-fact receive the correct values
- verify(stackScrollerController).setDozeAmount(dozeAmount)
- verify(stackScrollerController).setHideAmount(hideAmount, hideAmount)
+ verify(stackScrollerController).setDozeAmount(eased(dozeAmount))
+ verify(stackScrollerController).setHideAmount(hideAmount, eased(hideAmount))
// Now verify that there was just this ONE call to each of these methods
verify(stackScrollerController).setDozeAmount(anyFloat())
verify(stackScrollerController).setHideAmount(anyFloat(), anyFloat())
+ // clear for next check
+ clearInvocations(stackScrollerController)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 33a813f5b0ea..64f071039c74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -311,6 +311,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private ViewRootImpl mViewRootImpl;
@Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Mock private UserTracker mUserTracker;
+ @Mock private FingerprintManager mFingerprintManager;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@Mock IPowerManager mPowerManagerService;
@@ -521,7 +522,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCameraLauncherLazy,
() -> mLightRevealScrimViewModel,
mAlternateBouncerInteractor,
- mUserTracker
+ mUserTracker,
+ () -> mFingerprintManager
) {
@Override
protected ViewRootImpl getViewRootImpl() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 0cca7b2aa38c..1880c2bd16a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule;
+import androidx.core.animation.AnimatorTestRule2;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
@Before
public void setUp() throws Exception {