Merge changes from topic "tsuji-wakeup" into tm-qpr-dev am: e29ff09035

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22325397

Change-Id: If044489ed5903d85242748b4aeb6b018fb45cacb
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Jeff DeCew
2023-03-30 16:35:25 +00:00
committed by Automerger Merge Worker
14 changed files with 1037 additions and 181 deletions

View File

@@ -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();
}

View File

@@ -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,33 +290,109 @@ 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)
updateHideAmount()
if (changed && linear == 0.0f) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
setNotificationsVisibleForExpansion(visible = false, animate = false,
increaseSpeed = false)
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 && outputLinearDozeAmount == 0.0f) {
setNotificationsVisible(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) {
@@ -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
}
}
}
}

View File

@@ -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"

View File

@@ -5565,6 +5565,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
updateContinuousBackgroundDrawing();
updateStackPosition();
requestChildrenUpdate();
}

View File

@@ -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;

View File

@@ -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();
@@ -3505,7 +3521,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();
}
@@ -3532,7 +3585,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
@@ -4396,4 +4449,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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -436,6 +436,34 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
assertKeyguardStatusViewCentered();
}
@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);

View File

@@ -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() {

View File

@@ -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)
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.logSetHideAmount(0f)
logger.logSetHideAmount(0f)
verifyDidLog(0)
logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false)
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 setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() {
logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
fun updateDozeAmountWillThrottleFractionalDelayUpdates() {
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(0f, 0.1f, 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(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.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false)
logger.logUpdateDozeAmount(0f, 1f, null, 1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
}
@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.logUpdateDozeAmount(1.0f, 0.0f, 1f, 1f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
}
@Test
fun updateDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
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, 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)
}

View File

@@ -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)
}
}

View File

@@ -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() {

View File

@@ -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 {