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
This commit is contained in:
Josh Tsuji
2023-03-24 16:22:48 -04:00
committed by Jeff DeCew
parent 7a42fd9b42
commit 319f4d7785
6 changed files with 156 additions and 8 deletions

View File

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

View File

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

View File

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

View File

@@ -3564,6 +3564,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(
mShouldDelayWakeUpAnimation);
mWakeUpCoordinator.setWakingUp(
/* wakingUp= */ true,
mShouldDelayWakeUpAnimation);

View File

@@ -436,6 +436,34 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
assertKeyguardStatusViewCentered();
}
@Test
public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
mStatusBarStateController.setState(KEYGUARD);
enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
assertKeyguardStatusViewCentered();
mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
assertKeyguardStatusViewNotCentered();
}
@Test
public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
mStatusBarStateController.setState(KEYGUARD);
enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
assertKeyguardStatusViewCentered();
mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
assertKeyguardStatusViewCentered();
}
@Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);

View File

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