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
Merged-In: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
Change-Id: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
This commit is contained in:
Josh Tsuji
2022-01-21 13:42:54 -05:00
parent fd16e8c352
commit 7ecf6bb70a
2 changed files with 95 additions and 8 deletions

View File

@@ -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
// 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()
// Show AOD. That'll cause the KeyguardVisibilityHelper to call
// #animateInKeyguard.
statusBar.notificationPanelViewController.showAodUi()
}
}, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
} else {
decidedToAnimateGoingToSleep = false

View File

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