From 15cddc9d3ee5c906c0c4b4a8db021bbf7d4c4ffa Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Thu, 23 Feb 2023 14:36:53 -0500 Subject: [PATCH 1/6] Tell NotificationWakeupCoordinator if we're waking from power button w/ SFPS press-to-unlock. Bug: 269085199 Test: wake up with/without power button, with/without touch to unlock anytime enabled Change-Id: If0c7c5ef4e489ec3ff595e7f852c50b41644fa90 --- .../NotificationPanelViewController.java | 2 + .../NotificationWakeUpCoordinator.kt | 21 ++++- .../NotificationWakeUpCoordinatorLogger.kt | 9 +++ .../statusbar/phone/CentralSurfaces.java | 4 + .../statusbar/phone/CentralSurfacesImpl.java | 80 ++++++++++++++++--- ...NotificationWakeUpCoordinatorLoggerTest.kt | 2 +- .../NotificationWakeUpCoordinatorTest.kt | 2 +- .../phone/CentralSurfacesImplTest.java | 4 +- 8 files changed, 111 insertions(+), 13 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index ad8ae75edc236..548fd27c55b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -253,6 +253,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; @@ -275,6 +276,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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 8874f59d6c175..29b1bd9cec1a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -84,7 +84,7 @@ class NotificationWakeUpCoordinator @Inject constructor( var fullyAwake: Boolean = false var wakingUp = false - set(value) { + private set(value) { field = value willWakeUp = false if (value) { @@ -294,6 +294,21 @@ class NotificationWakeUpCoordinator @Inject constructor( } } + /** + * 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. + */ + fun setWakingUp( + wakingUp: Boolean, + requestDelayedAnimation: Boolean, + ) { + this.wakingUp = wakingUp + // TODO(jeffdq): Delay the wakeup animation if we're pressing to unlock. + } + override fun onStateChanged(newState: Int) { logger.logOnStateChanged(newState = newState, storedState = state) if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) { @@ -500,6 +515,10 @@ class NotificationWakeUpCoordinator @Inject constructor( pw.println("canShowPulsingHuns: $canShowPulsingHuns") } + fun logClockTransitionAnimationStarting(delayWakeUpAnimation: Boolean) { + logger.logClockTransitionAnimationStarting(delayWakeUpAnimation) + } + interface WakeUpListener { /** * Called whenever the notifications are fully hidden or shown diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index 88d9ffcdcf3e8..0634c8a00b4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -114,6 +114,15 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) { } ) } + + fun logClockTransitionAnimationStarting(delayWakeUpAnimation: Boolean) { + buffer.log( + TAG, + DEBUG, + { bool1 = delayWakeUpAnimation }, + { "clockTransitionAnimationStarting() withDelay=$bool1" } + ) + } } private const val TAG = "NotificationWakeUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 311728fff6aa9..b62450b22889b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -53,6 +53,7 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.util.Compile; import java.io.PrintWriter; @@ -71,6 +72,7 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn boolean DEBUG_MEDIA_FAKE_ARTWORK = false; boolean DEBUG_CAMERA_LIFT = false; boolean DEBUG_WINDOW_STATE = false; + boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG; // additional instrumentation for testing purposes; intended to be left on during development boolean CHATTY = DEBUG; boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; @@ -536,6 +538,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void extendDozePulse(); + boolean shouldDelayWakeUpAnimation(); + public static class KeyboardShortcutsMessage { final int mDeviceId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 191302547458c..1fb9c5b60b091 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -69,6 +69,7 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.net.Uri; import android.os.Binder; @@ -257,6 +258,7 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; import dagger.Lazy; @@ -446,10 +448,20 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @VisibleForTesting DozeServiceHost mDozeServiceHost; - private boolean mWakeUpComingFromTouch; private LightRevealScrim mLightRevealScrim; private PowerButtonReveal mPowerButtonReveal; + private boolean mWakeUpComingFromTouch; + + /** + * Whether we should delay the wakeup animation (which shows the notifications and moves the + * clock view). This is typically done when waking up from a 'press to unlock' gesture on a + * device with a side fingerprint sensor, so that if the fingerprint scan is successful, we + * can play the unlock animation directly rather than interrupting the wakeup animation part + * way through. + */ + private boolean mShouldDelayWakeUpAnimation = false; + private final Object mQueueLock = new Object(); private final PulseExpansionHandler mPulseExpansionHandler; @@ -510,6 +522,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final MessageRouter mMessageRouter; private final WallpaperManager mWallpaperManager; private final UserTracker mUserTracker; + private final Provider mFingerprintManager; private CentralSurfacesComponent mCentralSurfacesComponent; @@ -753,7 +766,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Lazy cameraLauncherLazy, Lazy lightRevealScrimViewModelLazy, AlternateBouncerInteractor alternateBouncerInteractor, - UserTracker userTracker + UserTracker userTracker, + Provider 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; @@ -3149,6 +3164,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"); @@ -3159,11 +3178,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(); @@ -3516,7 +3532,42 @@ 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"); + } + } + + mWakeUpCoordinator.setWakingUp( + /* wakingUp= */ true, + mShouldDelayWakeUpAnimation); + if (!mKeyguardBypassController.getBypassEnabled()) { mHeadsUpManager.releaseAllImmediately(); } @@ -3543,7 +3594,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 @@ -4407,4 +4458,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } return mUserTracker.getUserHandle(); } + + /** + * Whether we want to animate the wake animation AOD to lockscreen. This is done only if the + * doze service host says we can, and also we're not wake and unlocking (in which case the + * AOD instantly hides). + */ + private boolean shouldAnimateDozeWakeup() { + return mDozeServiceHost.shouldAnimateWakeup() + && mBiometricUnlockController.getMode() + != BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index 7a6779684fc5c..bbed2c5126eea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogcatEchoTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 95591a4b321cd..d055ebe1957c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -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.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index bf05d4e43224c..c33438a164826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -311,6 +311,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private ViewRootImpl mViewRootImpl; @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; @Mock private UserTracker mUserTracker; + @Mock private FingerprintManager mFingerprintManager; @Captor private ArgumentCaptor mOnBackInvokedCallback; @Mock IPowerManager mPowerManagerService; @@ -521,7 +522,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCameraLauncherLazy, () -> mLightRevealScrimViewModel, mAlternateBouncerInteractor, - mUserTracker + mUserTracker, + () -> mFingerprintManager ) { @Override protected ViewRootImpl getViewRootImpl() { From 9e0349d256e30c4bf86f0bee17572a3718016ed2 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 24 Feb 2023 17:50:20 +0000 Subject: [PATCH 2/6] Restructure the NotificationWakeUpCoordinator's dozeAmount recalculation. Bug: 269085199 Test: atest NotificationWakeUpCoordinatorTest NotificationWakeUpCoordinatorLoggerTest Change-Id: Ib7598a3faf0fd8738b24eda7620e5460a75c8c13 --- .../NotificationWakeUpCoordinator.kt | 134 +++++++++++------- .../NotificationWakeUpCoordinatorLogger.kt | 40 ++++-- ...NotificationWakeUpCoordinatorLoggerTest.kt | 52 +++---- .../NotificationWakeUpCoordinatorTest.kt | 16 ++- 4 files changed, 151 insertions(+), 91 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 29b1bd9cec1a6..44fb87669a825 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification import android.animation.ObjectAnimator import android.util.FloatProperty +import android.view.animation.Interpolator import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.animation.Interpolators @@ -66,10 +67,16 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 + /** 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 @@ -104,7 +111,7 @@ class NotificationWakeUpCoordinator @Inject constructor( var willWakeUp = false set(value) { - if (!value || mDozeAmount != 0.0f) { + if (!value || outputLinearDozeAmount != 0.0f) { field = value } } @@ -156,7 +163,7 @@ class NotificationWakeUpCoordinator @Inject constructor( 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() + maybeClearHardDozeAmountOverrideHidingNotifs() } } @@ -229,7 +236,8 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 +265,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,29 +275,52 @@ class NotificationWakeUpCoordinator @Inject constructor( return } - if (linear != 1.0f && linear != 0.0f && - (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) { - // Let's notify the scroller that an animation started - notifyAnimationStart(mLinearDozeAmount == 1.0f) + if (clearHardDozeAmountOverride()) { + return } - setDozeAmount(linear, eased, source = "StatusBar") + + updateDozeAmount() } - fun setDozeAmount( - linear: Float, - eased: Float, - source: String, - hidesNotifsByOverride: Boolean = false - ) { - val changed = linear != mLinearDozeAmount - logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed) - mLinearDozeAmount = linear - mDozeAmount = eased - mDozeAmountSource = source - mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride - mStackScrollerController.setDozeAmount(mDozeAmount) + private fun setHardDozeAmountOverride(dozing: Boolean, source: String) { + logger.logSetDozeAmountOverride(dozing = dozing, source = source) + hardDozeAmountOverride = if (dozing) 1f else 0f + hardDozeAmountOverrideSource = source + updateDozeAmount() + } + + private fun clearHardDozeAmountOverride(): Boolean { + if (hardDozeAmountOverride == null) return false + hardDozeAmountOverride = null + hardDozeAmountOverrideSource = "Cleared: $hardDozeAmountOverrideSource" + updateDozeAmount() + return true + } + + private fun updateDozeAmount() { + // Calculate new doze amount (linear) + val newOutputLinearDozeAmount = hardDozeAmountOverride ?: inputLinearDozeAmount + 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, + hardOverride = hardDozeAmountOverride, + outputLinear = outputLinearDozeAmount, + state = statusBarStateController.state, + changed = changed) + mStackScrollerController.setDozeAmount(outputEasedDozeAmount) updateHideAmount() - if (changed && linear == 0.0f) { + if (changed && outputLinearDozeAmount == 0.0f) { setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) setNotificationsVisibleForExpansion(visible = false, animate = false, increaseSpeed = false) @@ -317,12 +350,13 @@ 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 } @@ -332,7 +366,7 @@ class NotificationWakeUpCoordinator @Inject constructor( return } - maybeClearDozeAmountOverrideHidingNotifs() + maybeClearHardDozeAmountOverrideHidingNotifs() this.state = newState } @@ -360,10 +394,9 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 } @@ -377,8 +410,8 @@ 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 @@ -390,13 +423,13 @@ class NotificationWakeUpCoordinator @Inject constructor( // !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( + logger.logMaybeClearHardDozeAmountOverrideHidingNotifs( willRemove = willRemove, onKeyguard = onKeyguard, dozing = dozing, bypass = bypass, animating = animating, ) if (willRemove) { - setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource") + clearHardDozeAmountOverride() } } } @@ -409,10 +442,9 @@ class NotificationWakeUpCoordinator @Inject constructor( * @return Whether the doze amount was overridden because we are playing the screen off * 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 } @@ -428,12 +460,12 @@ class NotificationWakeUpCoordinator @Inject constructor( } val target = if (mNotificationsVisible) 1.0f else 0.0f val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target) - visibilityAnimator.setInterpolator(Interpolators.LINEAR) + visibilityAnimator.interpolator = Interpolators.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 } @@ -447,15 +479,15 @@ class NotificationWakeUpCoordinator @Inject constructor( } 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) mStackScrollerController.setHideAmount(linearAmount, amount) notificationsFullyHidden = linearAmount == 1.0f } @@ -473,7 +505,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 @@ -495,10 +527,12 @@ class NotificationWakeUpCoordinator @Inject constructor( dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking override fun dump(pw: PrintWriter, args: Array) { - 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("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") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index 0634c8a00b4fb..d95d18ebba8cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -24,48 +24,60 @@ class NotificationWakeUpCoordinatorLogger constructor(@NotificationLockscreenLog private val buffer: LogBuffer) { private var lastSetDozeAmountLogWasFractional = false private var lastSetDozeAmountLogState = -1 - private var lastSetDozeAmountLogSource = "undefined" + private var lastSetHardOverride: Float? = null private var lastOnDozeAmountChangedLogWasFractional = false - fun logSetDozeAmount( - linear: Float, - eased: Float, - source: String, + fun logUpdateDozeAmount( + inputLinear: 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 isFractional = inputLinear != 1f && inputLinear != 0f if ( lastSetDozeAmountLogWasFractional && isFractional && lastSetDozeAmountLogState == state && - lastSetDozeAmountLogSource == source + lastSetHardOverride == hardOverride ) { return } lastSetDozeAmountLogWasFractional = isFractional 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() int1 = state bool1 = changed }, { - "setDozeAmount(linear=$double1, eased=$str2, source=$str3)" + + "updateDozeAmount() inputLinear=$double1 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,7 +92,7 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) { "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" + " bypass=$bypass animating=$animating" }, - { "maybeClearDozeAmountOverrideHidingNotifs() $str1" } + { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" } ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index bbed2c5126eea..88f4ae98c54f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -45,47 +45,51 @@ class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() { } @Test - fun setDozeAmountWillThrottleFractionalUpdates() { - logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + fun updateDozeAmountWillThrottleFractionalInputUpdates() { + logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false) verifyDidLog(1) - logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + logger.logUpdateDozeAmount(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(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) verifyDidLog(0) - logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true) + logger.logUpdateDozeAmount(1f, null, 1f, StatusBarState.SHADE, changed = true) verifyDidLog(1) } @Test - fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() { - logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + fun updateDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() { + logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false) verifyDidLog(1) - logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + logger.logUpdateDozeAmount(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(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) verifyDidLog(0) - logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false) + logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.KEYGUARD, changed = false) verifyDidLog(1) } @Test - fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() { - logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false) + fun updateDozeAmountWillIncludeFractionalUpdatesWhenHardOverrideChanges() { + logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false) verifyDidLog(1) - logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true) + logger.logUpdateDozeAmount(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(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) verifyDidLog(0) - logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false) + logger.logUpdateDozeAmount(0.5f, 1f, 1f, StatusBarState.SHADE, changed = true) + verifyDidLog(1) + logger.logUpdateDozeAmount(0.5f, 0f, 0f, StatusBarState.SHADE, changed = true) + verifyDidLog(1) + logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true) verifyDidLog(1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index d055ebe1957c1..df98f87896673 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -58,6 +58,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 setBypassEnabled(enabled: Boolean) { bypassEnabled = enabled @@ -121,7 +123,11 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { @Test fun setDozeToHalfWillHalfShowNotifications() { setDozeAmount(0.5f) - verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f) + verifyStackScrollerDozeAndHideAmount( + dozeAmount = easedDozeAmount, + hideAmount = 0.5f, + hideAmountEased = easedDozeAmount, + ) assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse() } @@ -152,10 +158,14 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { assertThat(notificationWakeUpCoordinator.statusBarState).isEqualTo(StatusBarState.SHADE) } - private fun verifyStackScrollerDozeAndHideAmount(dozeAmount: Float, hideAmount: Float) { + private fun verifyStackScrollerDozeAndHideAmount( + dozeAmount: Float, + hideAmount: Float, + hideAmountEased: Float? = null, + ) { // First verify that we did in-fact receive the correct values verify(stackScrollerController).setDozeAmount(dozeAmount) - verify(stackScrollerController).setHideAmount(hideAmount, hideAmount) + verify(stackScrollerController).setHideAmount(hideAmount, hideAmountEased ?: hideAmount) // Now verify that there was just this ONE call to each of these methods verify(stackScrollerController).setDozeAmount(anyFloat()) verify(stackScrollerController).setHideAmount(anyFloat(), anyFloat()) From 408b55fecae3b32177806e0172830a3e0bd0f2c0 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Thu, 9 Mar 2023 15:01:33 +0000 Subject: [PATCH 3/6] Run ktfmt on NotificationWakeUpCoordinator.kt Bug: 269085199 Test: n/a Change-Id: I48819780d4e17f59bfa50327a4e239f10d2fe572 --- .../NotificationWakeUpCoordinator.kt | 172 ++++++++++-------- 1 file changed, 100 insertions(+), 72 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 44fb87669a825..395e946cf6db6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -42,7 +42,9 @@ import javax.inject.Inject import kotlin.math.min @SysUISingleton -class NotificationWakeUpCoordinator @Inject constructor( +class NotificationWakeUpCoordinator +@Inject +constructor( dumpManager: DumpManager, private val mHeadsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController, @@ -50,20 +52,23 @@ 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( - "notificationVisibility") { + private val mNotificationVisibility = + object : FloatProperty("notificationVisibility") { - override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { - coordinator.setVisibilityAmount(value) - } + override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { + coordinator.setVisibilityAmount(value) + } - override fun get(coordinator: NotificationWakeUpCoordinator): Float? { - return coordinator.mLinearVisibilityAmount + override fun get(coordinator: NotificationWakeUpCoordinator): Float? { + return coordinator.mLinearVisibilityAmount + } } - } private lateinit var mStackScrollerController: NotificationStackScrollLayoutController private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE @@ -74,8 +79,7 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 + @VisibleForTesting val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN private var mNotificationVisibleAmount = 0.0f private var mNotificationsVisible = false @@ -95,16 +99,21 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 + ) } } } @@ -125,8 +134,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 + ) } } @@ -140,17 +151,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 @@ -159,30 +170,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. - maybeClearHardDozeAmountOverrideHidingNotifs() + 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) { @@ -228,16 +247,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) && - outputLinearDozeAmount != 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 @@ -303,8 +323,11 @@ class NotificationWakeUpCoordinator @Inject constructor( val changed = outputLinearDozeAmount != newOutputLinearDozeAmount // notify when the animation is starting - if (newOutputLinearDozeAmount != 1.0f && newOutputLinearDozeAmount != 0.0f && - (outputLinearDozeAmount == 0.0f || outputLinearDozeAmount == 1.0f)) { + 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) } @@ -313,17 +336,21 @@ class NotificationWakeUpCoordinator @Inject constructor( outputLinearDozeAmount = newOutputLinearDozeAmount outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount) logger.logUpdateDozeAmount( - inputLinear = inputLinearDozeAmount, - hardOverride = hardDozeAmountOverride, - outputLinear = outputLinearDozeAmount, - state = statusBarStateController.state, - changed = changed) + inputLinear = inputLinearDozeAmount, + 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) + setNotificationsVisibleForExpansion( + visible = false, + animate = false, + increaseSpeed = false + ) } } @@ -335,8 +362,8 @@ class NotificationWakeUpCoordinator @Inject constructor( * the wakeup animation looks chaotic. */ fun setWakingUp( - wakingUp: Boolean, - requestDelayedAnimation: Boolean, + wakingUp: Boolean, + requestDelayedAnimation: Boolean, ) { this.wakingUp = wakingUp // TODO(jeffdq): Delay the wakeup animation if we're pressing to unlock. @@ -351,7 +378,9 @@ class NotificationWakeUpCoordinator @Inject constructor( // the doze amount to 0f (not dozing) so that the notifications are no longer hidden. // See: UnlockedScreenOffAnimationController.onFinishedWakingUp() setHardDozeAmountOverride( - dozing = false, source = "Override: Shade->Shade (lock cancelled by unlock)") + dozing = false, + source = "Override: Shade->Shade (lock cancelled by unlock)" + ) this.state = newState return } @@ -389,7 +418,7 @@ 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) { @@ -416,7 +445,7 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 @@ -424,9 +453,11 @@ class NotificationWakeUpCoordinator @Inject constructor( // notifications to be visible, and thus it is safe to unhide them. val willRemove = (!onKeyguard || !dozing) && !bypass && !animating logger.logMaybeClearHardDozeAmountOverrideHidingNotifs( - willRemove = willRemove, - onKeyguard = onKeyguard, dozing = dozing, - bypass = bypass, animating = animating, + willRemove = willRemove, + onKeyguard = onKeyguard, + dozing = dozing, + bypass = bypass, + animating = animating, ) if (willRemove) { clearHardDozeAmountOverride() @@ -440,7 +471,7 @@ 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(): Boolean { if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) { @@ -453,10 +484,9 @@ 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) @@ -472,8 +502,7 @@ class NotificationWakeUpCoordinator @Inject constructor( private fun setVisibilityAmount(visibilityAmount: Float) { mLinearVisibilityAmount = visibilityAmount - mVisibilityAmount = mVisibilityInterpolator.getInterpolation( - visibilityAmount) + mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount) handleAnimationFinished() updateHideAmount() } @@ -524,7 +553,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } private fun shouldAnimateVisibility() = - dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking + dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking override fun dump(pw: PrintWriter, args: Array) { pw.println("inputLinearDozeAmount: $inputLinearDozeAmount") @@ -554,13 +583,12 @@ class NotificationWakeUpCoordinator @Inject constructor( } 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) {} From 7a42fd9b42848a3ee4039fee08b6384da1ff18b6 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Thu, 9 Mar 2023 15:02:48 +0000 Subject: [PATCH 4/6] 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 --- .../NotificationWakeUpCoordinator.kt | 81 ++++++++--- .../NotificationWakeUpCoordinatorLogger.kt | 82 +++++++++-- .../stack/NotificationStackScrollLayout.java | 1 + ...NotificationWakeUpCoordinatorLoggerTest.kt | 127 ++++++++++++++---- .../NotificationWakeUpCoordinatorTest.kt | 112 ++++++++++++--- 5 files changed, 337 insertions(+), 66 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 395e946cf6db6..704c906911bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -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("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) { 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("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("delayedDozeAmount") { + + override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { + coordinator.delayedDozeAmountOverride = value + coordinator.logger.logSetDelayDozeAmountOverride(value) + coordinator.updateDozeAmount() + } + + override fun get(coordinator: NotificationWakeUpCoordinator): Float { + return coordinator.delayedDozeAmountOverride + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index d95d18ebba8cb..d3f935366f958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 2c088fa66a8a0..300587627c185 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5565,6 +5565,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setDozeAmount(float dozeAmount) { mAmbientState.setDozeAmount(dozeAmount); updateContinuousBackgroundDrawing(); + updateStackPosition(); requestChildrenUpdate(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index 88f4ae98c54f5..bef9fcb5697cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -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) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index df98f87896673..6df62bb539d71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -17,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) } } From 319f4d7785e95402e0fd4afd76efe676fb5efe01 Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Fri, 24 Mar 2023 16:22:48 -0400 Subject: [PATCH 5/6] Keep the clock centered until the delayed doze animation plays. This prevents the clock from moving until the notifications come down. Bug: 269085199 Test: atest NotificationPanelViewTest Change-Id: Iaf3f5f6ba3dd5b7a62b4e41d857069f7c8cb888e --- .../NotificationPanelViewController.java | 29 ++++++++ .../NotificationWakeUpCoordinator.kt | 26 +++++-- .../NotificationWakeUpCoordinatorLogger.kt | 6 +- .../statusbar/phone/CentralSurfacesImpl.java | 2 + .../NotificationPanelViewControllerTest.java | 28 +++++++ .../NotificationWakeUpCoordinatorTest.kt | 73 +++++++++++++++++++ 6 files changed, 156 insertions(+), 8 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 548fd27c55b2e..b59dda9a22e78 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -589,6 +589,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; @@ -1045,6 +1051,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 -> { @@ -1641,11 +1653,28 @@ public final class NotificationPanelViewController implements Dumpable { // Pulsing notification appears on the right. Move clock left to avoid overlap. return false; } + if (mWillPlayDelayedDozeAmountAnimation) { + return true; + } // "Visible" notifications are actually not visible on AOD (unless pulsing), so it is safe // to center the clock without overlap. return isOnAod(); } + /** + * Notify us that {@link NotificationWakeUpCoordinator} is going to play the doze wakeup + * animation after a delay. If so, we'll keep the clock centered until that animation starts. + */ + public void setWillPlayDelayedDozeAmountAnimation(boolean willPlay) { + if (mWillPlayDelayedDozeAmountAnimation == willPlay) return; + + mWillPlayDelayedDozeAmountAnimation = willPlay; + mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + + // Once changing this value, see if we should move the clock. + positionClockAndNotifications(); + } + private boolean isOnAod() { return mDozing && mDozeParameters.getAlwaysOn(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 704c906911bd4..20af6cadeed53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -39,8 +39,8 @@ 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 com.android.systemui.util.doOnStart import java.io.PrintWriter import javax.inject.Inject import kotlin.math.max @@ -357,6 +357,11 @@ constructor( * [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, @@ -379,8 +384,13 @@ constructor( interpolator = InterpolatorsAndroidX.LINEAR duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() startDelay = WAKEUP_ANIMATION_DELAY_MS.toLong() - doOnEnd { delayedDozeAmountAnimator = null } - doOnCancel { delayedDozeAmountAnimator = null } + doOnStart { + wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(true) } + } + doOnEnd { + delayedDozeAmountAnimator = null + wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(false) } + } start() } } @@ -597,8 +607,8 @@ constructor( pw.println("canShowPulsingHuns: $canShowPulsingHuns") } - fun logClockTransitionAnimationStarting(delayWakeUpAnimation: Boolean) { - logger.logClockTransitionAnimationStarting(delayWakeUpAnimation) + fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) { + logger.logDelayingClockWakeUpAnimation(delayingAnimation) } interface WakeUpListener { @@ -611,6 +621,12 @@ constructor( * @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 { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index d3f935366f958..dd3c2a9df3e56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -193,12 +193,12 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) { ) } - fun logClockTransitionAnimationStarting(delayWakeUpAnimation: Boolean) { + fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) { buffer.log( TAG, DEBUG, - { bool1 = delayWakeUpAnimation }, - { "clockTransitionAnimationStarting() withDelay=$bool1" } + { bool1 = delayingAnimation }, + { "logDelayingClockWakeUpAnimation($bool1)" } ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 1fb9c5b60b091..0ba546cbb2d02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3564,6 +3564,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation( + mShouldDelayWakeUpAnimation); mWakeUpCoordinator.setWakingUp( /* wakingUp= */ true, mShouldDelayWakeUpAnimation); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index b868018b7dda2..be0d933e2c5b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 6df62bb539d71..aff705f1f3bfe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -31,6 +31,7 @@ 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 @@ -43,6 +44,7 @@ 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 @@ -59,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 @@ -207,6 +210,76 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { 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() From 6de9be1669aea06ede10c0b62b1edbd1a960725c Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Tue, 28 Mar 2023 20:52:51 +0000 Subject: [PATCH 6/6] Create AnimatorTestRule2 which avoids the test interaction bugs with the original as it currently exists Bug: 269085199 Bug: 275602127 Test: atest SystemStatusAnimationSchedulerImplTest NotificationWakeUpCoordinatorTest Change-Id: Ib28b96ea7e19a57bf21ef66f6d7dd1e563a200ad --- .../core/animation/AnimatorTestRule2.java | 174 ++++++++++++++++++ .../core/animation/AnimatorTestRuleTest.kt | 77 ++++++++ .../SystemStatusAnimationSchedulerImplTest.kt | 4 +- .../NotificationWakeUpCoordinatorTest.kt | 4 +- .../statusbar/policy/RemoteInputViewTest.java | 4 +- 5 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java create mode 100644 packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java new file mode 100644 index 0000000000000..e93e862915354 --- /dev/null +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.core.animation; + +import android.os.Looper; +import android.os.SystemClock; +import android.util.AndroidRuntimeException; + +import androidx.annotation.NonNull; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.List; + +/** + * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to + * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static + * list of callbacks. + * + * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code. + */ +public final class AnimatorTestRule2 implements TestRule { + + class TestAnimationHandler extends AnimationHandler { + TestAnimationHandler() { + super(new TestProvider()); + } + + List animationCallbacks = new ArrayList<>(); + + @Override + void addAnimationFrameCallback(AnimationFrameCallback callback) { + animationCallbacks.add(callback); + callback.doAnimationFrame(getCurrentTime()); + } + + @Override + public void removeCallback(AnimationFrameCallback callback) { + int id = animationCallbacks.indexOf(callback); + if (id >= 0) { + animationCallbacks.set(id, null); + } + } + + void onAnimationFrame(long frameTime) { + for (int i = 0; i < animationCallbacks.size(); i++) { + final AnimationFrameCallback callback = animationCallbacks.get(i); + if (callback == null) { + continue; + } + callback.doAnimationFrame(frameTime); + } + } + + @Override + void autoCancelBasedOn(ObjectAnimator objectAnimator) { + for (int i = animationCallbacks.size() - 1; i >= 0; i--) { + AnimationFrameCallback cb = animationCallbacks.get(i); + if (cb == null) { + continue; + } + if (objectAnimator.shouldAutoCancel(cb)) { + ((Animator) animationCallbacks.get(i)).cancel(); + } + } + } + } + + final TestAnimationHandler mTestHandler; + final long mStartTime; + private long mTotalTimeDelta = 0; + private final Object mLock = new Object(); + + public AnimatorTestRule2() { + mStartTime = SystemClock.uptimeMillis(); + mTestHandler = new TestAnimationHandler(); + } + + @NonNull + @Override + public Statement apply(@NonNull final Statement base, @NonNull Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + AnimationHandler.setTestHandler(mTestHandler); + try { + base.evaluate(); + } finally { + AnimationHandler.setTestHandler(null); + } + } + }; + } + + /** + * Advances the animation clock by the given amount of delta in milliseconds. This call will + * produce an animation frame to all the ongoing animations. This method needs to be + * called on the same thread as {@link Animator#start()}. + * + * @param timeDelta the amount of milliseconds to advance + */ + public void advanceTimeBy(long timeDelta) { + if (Looper.myLooper() == null) { + // Throw an exception + throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be" + + "called on Looper threads"); + } + synchronized (mLock) { + // Advance time & pulse a frame + mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta; + } + // produce a frame + mTestHandler.onAnimationFrame(getCurrentTime()); + } + + + /** + * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a + * different time than the time tracked by {@link SystemClock} This method needs to be called on + * the same thread as {@link Animator#start()}. + */ + public long getCurrentTime() { + if (Looper.myLooper() == null) { + // Throw an exception + throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be" + + "called on Looper threads"); + } + synchronized (mLock) { + return mStartTime + mTotalTimeDelta; + } + } + + + private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider { + TestProvider() { + } + + @Override + public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) { + callback.doAnimationFrame(getCurrentTime()); + } + + @Override + public void postFrameCallback() { + } + + @Override + public void setFrameDelay(long delay) { + } + + @Override + public long getFrameDelay() { + return 0; + } + } +} + diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt new file mode 100644 index 0000000000000..bddd60b5970a8 --- /dev/null +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.core.animation + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.doOnEnd +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper(setAsMainLooper = true) +class AnimatorTestRuleTest : SysuiTestCase() { + + @get:Rule val animatorTestRule = AnimatorTestRule2() + + @Test + fun testA() { + didTouchA = false + didTouchB = false + ObjectAnimator.ofFloat(0f, 1f).apply { + duration = 100 + doOnEnd { didTouchA = true } + start() + } + ObjectAnimator.ofFloat(0f, 1f).apply { + duration = 150 + doOnEnd { didTouchA = true } + start() + } + animatorTestRule.advanceTimeBy(100) + assertThat(didTouchA).isTrue() + assertThat(didTouchB).isFalse() + } + + @Test + fun testB() { + didTouchA = false + didTouchB = false + ObjectAnimator.ofFloat(0f, 1f).apply { + duration = 100 + doOnEnd { didTouchB = true } + start() + } + ObjectAnimator.ofFloat(0f, 1f).apply { + duration = 150 + doOnEnd { didTouchB = true } + start() + } + animatorTestRule.advanceTimeBy(100) + assertThat(didTouchA).isFalse() + assertThat(didTouchB).isTrue() + } + + companion object { + var didTouchA = false + var didTouchB = false + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 08a9f3139d71e..7b59cc284181d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule +import androidx.core.animation.AnimatorTestRule2 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler private val fakeFeatureFlags = FakeFeatureFlags() - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule2() @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index aff705f1f3bfe..be3b7234a1a27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import android.testing.TestableLooper -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 @@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule2() private val dumpManager: DumpManager = mock() private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 0cca7b2aa38c4..1880c2bd16a22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; import androidx.annotation.NonNull; -import androidx.core.animation.AnimatorTestRule; +import androidx.core.animation.AnimatorTestRule2; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @ClassRule - public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2(); @Before public void setUp() throws Exception {