Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a20d7f27 authored by Josh Tsuji's avatar Josh Tsuji
Browse files

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
Change-Id: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
parent 69455cff
Loading
Loading
Loading
Loading
+16 −6
Original line number Diff line number Diff line
@@ -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
@@ -54,9 +55,10 @@ class UnlockedScreenOffAnimationController @Inject constructor(
    private val keyguardStateController: KeyguardStateController,
    private val dozeParameters: dagger.Lazy<DozeParameters>,
    private val globalSettings: GlobalSettings,
    private val interactionJankMonitor: InteractionJankMonitor
    private val interactionJankMonitor: InteractionJankMonitor,
    private val powerManager: PowerManager,
    private val handler: Handler = Handler()
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
    private val handler = Handler()

    private lateinit var statusBar: StatusBar
    private lateinit var lightRevealScrim: LightRevealScrim
@@ -231,10 +233,18 @@ class UnlockedScreenOffAnimationController @Inject constructor(
            lightRevealAnimationPlaying = true
            lightRevealAnimator.start()
            handler.postDelayed({
                // 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.
                    // Show AOD. That'll cause the KeyguardVisibilityHelper to call
                    // #animateInKeyguard.
                    statusBar.notificationPanelViewController.showAodUi()
                }
            }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())

            return true
+77 −3
Original line number Diff line number Diff line
@@ -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
@@ -29,13 +31,19 @@ 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.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@@ -53,7 +61,9 @@ 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
@@ -62,6 +72,10 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
    private lateinit var statusBarStateController: StatusBarStateControllerImpl
    @Mock
    private lateinit var interactionJankMonitor: InteractionJankMonitor
    @Mock
    private lateinit var powerManager: PowerManager
    @Mock
    private lateinit var handler: Handler

    @Before
    fun setUp() {
@@ -75,9 +89,24 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
                keyguardStateController,
                dagger.Lazy<DozeParameters> { dozeParameters },
                globalSettings,
                interactionJankMonitor
                interactionJankMonitor,
                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
@@ -93,4 +122,49 @@ 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.startAnimation()

        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.startAnimation()

        verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
        callbackCaptor.value.run()

        verify(notificationPanelViewController, never()).showAodUi()
    }
}
 No newline at end of file