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

Commit 955d37e6 authored by Josh Tsuji's avatar Josh Tsuji Committed by Android Build Coastguard Worker
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
Merged-In: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
Change-Id: I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
(cherry picked from commit 7ecf6bb7)
Merged-In:I7e2d1ca7e3c9487903f74fb06575cc0a36b9502c
parent 4e0b08b5
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
@@ -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({
                // 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())
        } else {
            decidedToAnimateGoingToSleep = false
+80 −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
@@ -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