diff options
author | Josh Tsuji <tsuji@google.com> | 2022-01-21 13:42:54 -0500 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-03-02 18:17:52 -0800 |
commit | e0d326d3fd2a169bf7538cd92f58182b90347343 (patch) | |
tree | a152eac4176e5d71341d4fea7483bac8c7aa5b7d | |
parent | 90819feb5a868a69ab1d296994e9128f8e07ad80 (diff) | |
download | base-e0d326d3fd2a169bf7538cd92f58182b90347343.tar.gz |
Check that PowerManager#isInteractive is false before showing the AOD UI for screen off.
The callback that runs after a delay during the screen off animation to
show the AOD UI after the light reveal animation is supposed to be
cancelled in onStartedWakingUp if the screen off is cancelled. However,
some bugs make it seem like this may not be happening, resulting in the
AOD UI being shown while we are awake and unlocked. This causes the shade
to look like the lock screen. It also causes a transparent shade
background and possibly an all-white screen.
While we have not been able to reproduce this under normal conditions, I
manually called showAodUi while unlocked/awake, and verified that these
conditions result. Since onStartedWakingUp is dispatched asynchronously,
it's possible that the device wakes up before the callback is cancelled.
By checking PowerManager's state directly, we can avoid this.
Fixes: 213794749
Test: atest SystemUITests
(cherry picked from commit 7ecf6bb70a4096dd2b8e884f530c7a69df097682)
(cherry picked from commit 955d37e6d7f8868d50154d47f12fec340d380ae6)
Merged-In: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
Change-Id: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
2 files changed, 96 insertions, 9 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 83ab8cb80b29..4ba794fe8408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -6,6 +6,7 @@ import android.animation.ValueAnimator import android.content.Context import android.database.ContentObserver import android.os.Handler +import android.os.PowerManager import android.provider.Settings import android.view.Surface import android.view.View @@ -50,9 +51,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, private val keyguardStateController: KeyguardStateController, private val dozeParameters: dagger.Lazy<DozeParameters>, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val powerManager: PowerManager, + private val handler: Handler = Handler() ) : WakefulnessLifecycle.Observer { - private val handler = Handler() private lateinit var statusBar: StatusBar private lateinit var lightRevealScrim: LightRevealScrim @@ -205,10 +207,18 @@ class UnlockedScreenOffAnimationController @Inject constructor( lightRevealAnimationPlaying = true lightRevealAnimator.start() handler.postDelayed({ - aodUiAnimationPlaying = true - - // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard. - statusBar.notificationPanelViewController.showAodUi() + // Only run this callback if the device is sleeping (not interactive). This callback + // is removed in onStartedWakingUp, but since that event is asynchronously + // dispatched, a race condition could make it possible for this callback to be run + // as the device is waking up. That results in the AOD UI being shown while we wake + // up, with unpredictable consequences. + if (!powerManager.isInteractive) { + aodUiAnimationPlaying = true + + // Show AOD. That'll cause the KeyguardVisibilityHelper to call + // #animateInKeyguard. + statusBar.notificationPanelViewController.showAodUi() + } }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong()) } else { decidedToAnimateGoingToSleep = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index a8a33dabf1aa..fb8c9858be0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone import android.animation.Animator +import android.os.Handler +import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View @@ -28,13 +30,20 @@ import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.GlobalSettings +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyLong +import org.mockito.Mockito.mockingDetails +import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -52,13 +61,19 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var globalSettings: GlobalSettings @Mock - private lateinit var statusbar: StatusBar + private lateinit var statusBar: StatusBar + @Mock + private lateinit var notificationPanelViewController: NotificationPanelViewController @Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var statusBarStateController: StatusBarStateControllerImpl + @Mock + private lateinit var powerManager: PowerManager + @Mock + private lateinit var handler: Handler @Before fun setUp() { @@ -71,9 +86,24 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator }, keyguardStateController, dagger.Lazy<DozeParameters> { dozeParameters }, - globalSettings + globalSettings, + powerManager, + handler = handler ) - controller.initialize(statusbar, lightRevealScrim) + controller.initialize(statusBar, lightRevealScrim) + `when`(statusBar.notificationPanelViewController).thenReturn( + notificationPanelViewController) + + // Screen off does not run if the panel is expanded, so we should say it's collapsed to test + // screen off. + `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + } + + @After + fun cleanUp() { + // Tell the screen off controller to cancel the animations and clean up its state, or + // subsequent tests will act unpredictably as the animator continues running. + controller.onStartedWakingUp() } @Test @@ -89,4 +119,51 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { listener.value.onAnimationEnd(null) Mockito.verify(animator).setListener(null) } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do show the AOD UI when the device is not woken up + * (PowerManager#isInteractive = false). + */ + @Test + fun testAodUiShownIfNotInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(false) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.onStartedGoingToSleep() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + + callbackCaptor.value.run() + + verify(notificationPanelViewController, times(1)).showAodUi() + } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do not show the AOD UI when the device is woken up during screen + * off (PowerManager#isInteractive = true). + */ + @Test + fun testAodUiNotShownIfInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(true) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.onStartedGoingToSleep() + + mockingDetails(handler).printInvocations() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + callbackCaptor.value.run() + + verify(notificationPanelViewController, never()).showAodUi() + } }
\ No newline at end of file |