Delay the dozeAmount change animation.

This change builds on the previous refactor to introduce an animated delay.  This delay animation is carefully crafted to avoid accidentally affecting doze amount state in any situation other than while the animation is running, without ever having to cancel the animation.

Bug: 269085199
Test: atest NotificationWakeUpCoordinatorTest NotificationWakeUpCoordinatorLoggerTest
Change-Id: I5946c59159f04b07c4a56e862e4b0ecc575849ce
This commit is contained in:
Jeff DeCew
2023-03-09 15:02:48 +00:00
parent 408b55feca
commit 7a42fd9b42
5 changed files with 337 additions and 66 deletions

View File

@@ -16,15 +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
@@ -37,8 +39,11 @@ 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.doOnCancel
import com.android.systemui.util.doOnEnd
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@SysUISingleton
@@ -57,23 +62,13 @@ constructor(
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 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"
@@ -319,7 +314,8 @@ constructor(
private fun updateDozeAmount() {
// Calculate new doze amount (linear)
val newOutputLinearDozeAmount = hardDozeAmountOverride ?: inputLinearDozeAmount
val newOutputLinearDozeAmount =
hardDozeAmountOverride ?: max(inputLinearDozeAmount, delayedDozeAmountOverride)
val changed = outputLinearDozeAmount != newOutputLinearDozeAmount
// notify when the animation is starting
@@ -337,6 +333,7 @@ constructor(
outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount)
logger.logUpdateDozeAmount(
inputLinear = inputLinearDozeAmount,
delayLinear = delayedDozeAmountOverride,
hardOverride = hardDozeAmountOverride,
outputLinear = outputLinearDozeAmount,
state = statusBarStateController.state,
@@ -365,8 +362,27 @@ constructor(
wakingUp: Boolean,
requestDelayedAnimation: Boolean,
) {
logger.logSetWakingUp(wakingUp, requestDelayedAnimation)
this.wakingUp = wakingUp
// TODO(jeffdq): Delay the wakeup animation if we're pressing to unlock.
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()
doOnEnd { delayedDozeAmountAnimator = null }
doOnCancel { delayedDozeAmountAnimator = null }
start()
}
}
override fun onStateChanged(newState: Int) {
@@ -489,8 +505,8 @@ constructor(
else Interpolators.FAST_OUT_SLOW_IN_REVERSE
}
val target = if (mNotificationsVisible) 1.0f else 0.0f
val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
visibilityAnimator.interpolator = 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()
@@ -501,6 +517,7 @@ constructor(
}
private fun setVisibilityAmount(visibilityAmount: Float) {
logger.logSetVisibilityAmount(visibilityAmount)
mLinearVisibilityAmount = visibilityAmount
mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount)
handleAnimationFinished()
@@ -517,6 +534,7 @@ constructor(
private fun updateHideAmount() {
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
}
@@ -558,6 +576,7 @@ constructor(
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("inputLinearDozeAmount: $inputLinearDozeAmount")
pw.println("inputEasedDozeAmount: $inputEasedDozeAmount")
pw.println("delayedDozeAmountOverride: $delayedDozeAmountOverride")
pw.println("hardDozeAmountOverride: $hardDozeAmountOverride")
pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource")
pw.println("outputLinearDozeAmount: $outputLinearDozeAmount")
@@ -593,4 +612,32 @@ constructor(
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: 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,29 +22,40 @@ 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 lastSetHardOverride: Float? = null
private var lastOnDozeAmountChangedLogWasFractional = false
private var lastSetDelayDozeAmountOverrideLogWasFractional = false
private var lastSetVisibilityAmountLogWasFractional = false
private var lastSetHideAmountLogWasFractional = false
private var lastSetHideAmount = -1f
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 = inputLinear != 1f && inputLinear != 0f
val isInputFractional = inputLinear != 1f && inputLinear != 0f
val isDelayFractional = delayLinear != 1f && delayLinear != 0f
if (
lastSetDozeAmountLogWasFractional &&
isFractional &&
(isInputFractional || isDelayFractional) &&
lastSetDozeAmountLogInputWasFractional == isInputFractional &&
lastSetDozeAmountLogDelayWasFractional == isDelayFractional &&
lastSetDozeAmountLogState == state &&
lastSetHardOverride == hardOverride
lastSetHardOverride == hardOverride &&
allowThrottle
) {
return
}
lastSetDozeAmountLogWasFractional = isFractional
lastSetDozeAmountLogInputWasFractional = isInputFractional
lastSetDozeAmountLogDelayWasFractional = isDelayFractional
lastSetDozeAmountLogState = state
lastSetHardOverride = hardOverride
@@ -55,11 +66,13 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
double1 = inputLinear.toDouble()
str1 = hardOverride.toString()
str2 = outputLinear.toString()
str3 = delayLinear.toString()
int1 = state
bool1 = changed
},
{
"updateDozeAmount() inputLinear=$double1 hardOverride=$str1 outputLinear=$str2" +
"updateDozeAmount() inputLinear=$double1 delayLinear=$str3" +
" hardOverride=$str1 outputLinear=$str2" +
" state=${StatusBarState.toString(int1)} changed=$bool1"
}
)
@@ -99,7 +112,7 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
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,
@@ -112,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,
@@ -127,6 +181,18 @@ 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 logClockTransitionAnimationStarting(delayWakeUpAnimation: Boolean) {
buffer.log(
TAG,

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

@@ -45,51 +45,130 @@ class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
}
@Test
fun updateDozeAmountWillThrottleFractionalInputUpdates() {
logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false)
fun updateVisibilityThrottleFractionalUpdates() {
logger.logSetVisibilityAmount(0f)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
logger.logSetVisibilityAmount(0.1f)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
logger.logSetVisibilityAmount(0.2f)
logger.logSetVisibilityAmount(0.3f)
logger.logSetVisibilityAmount(0.4f)
logger.logSetVisibilityAmount(0.5f)
verifyDidLog(0)
logger.logUpdateDozeAmount(1f, null, 1f, StatusBarState.SHADE, changed = true)
logger.logSetVisibilityAmount(1f)
verifyDidLog(1)
}
@Test
fun updateHideAmountThrottleFractionalOrRepeatedUpdates() {
logger.logSetHideAmount(0f)
verifyDidLog(1)
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)
}
@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, null, 0f, StatusBarState.SHADE, changed = false)
logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, null, 0.5f, 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.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.KEYGUARD, changed = false)
logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.KEYGUARD, changed = false)
verifyDidLog(1)
}
@Test
fun updateDozeAmountWillIncludeFractionalUpdatesWhenHardOverrideChanges() {
logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false)
logger.logUpdateDozeAmount(0f, 0f, null, 0f, StatusBarState.SHADE, changed = false)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.1f, 0f, null, 0.1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, null, 0.5f, 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.logUpdateDozeAmount(0.5f, 1f, 1f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, 0f, 1f, 1f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.5f, 0f, 0f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, 0f, 0f, 0f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
logger.logUpdateDozeAmount(0.5f, 0f, null, 0.5f, StatusBarState.SHADE, changed = true)
verifyDidLog(1)
}

View File

@@ -17,12 +17,16 @@
package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
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
@@ -32,16 +36,21 @@ 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
@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -57,9 +66,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
private var bypassEnabled: Boolean = false
private var statusBarState: Int = StatusBarState.KEYGUARD
private var dozeAmount: Float = 0f
private val easedDozeAmount: Float
get() = notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)
private fun eased(dozeAmount: Float) =
notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)
private fun setBypassEnabled(enabled: Boolean) {
bypassEnabled = enabled
@@ -72,7 +80,6 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
}
private fun setDozeAmount(dozeAmount: Float) {
this.dozeAmount = dozeAmount
statusBarStateCallback.onDozeAmountChanged(dozeAmount, dozeAmount)
}
@@ -123,11 +130,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
@Test
fun setDozeToHalfWillHalfShowNotifications() {
setDozeAmount(0.5f)
verifyStackScrollerDozeAndHideAmount(
dozeAmount = easedDozeAmount,
hideAmount = 0.5f,
hideAmountEased = easedDozeAmount,
)
verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
}
@@ -135,7 +138,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
fun setDozeToZeroWithBypassWillFullyHideNotifications() {
bypassEnabled = true
setDozeAmount(0f)
verifyStackScrollerDozeAndHideAmount(dozeAmount = 01f, hideAmount = 1f)
verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
}
@@ -158,16 +161,91 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
assertThat(notificationWakeUpCoordinator.statusBarState).isEqualTo(StatusBarState.SHADE)
}
private fun verifyStackScrollerDozeAndHideAmount(
dozeAmount: Float,
hideAmount: Float,
hideAmountEased: Float? = null,
) {
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 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, hideAmountEased ?: 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)
}
}