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

Commit 29098c74 authored by Fiona Campbell's avatar Fiona Campbell
Browse files

Check for Adj display groups to fallback to state=off

When an external display is connected, and the internal display times
out first, when the external display finally times out, and the internal
goes to AOD, there is a flicker that occurs on the screen.

One of the causes of this, is that the fallback of display policies is
DIM. We End up going from OFF->DIM->OFF->DOZE. By falling back to OFF in
this particular circumstance, we end up only going from OFF->DOZE which
is correct behaviour to help resolve the cause of this flicker.

Changes are guarded by bugfix flag, and we also validate that we are on
the default display group, and that other adjacent displays exist and
are all non-interactive before exercising this new code branch.

Additionally, there is an animation that plays, we want to skip this if
the default display is non-interactive.

The combination of these two changes completely removes the flicker.

Bug: 425275163
Bug: 436832920
Flag: com.android.server.power.feature.flags.separate_timeouts_flicker
Test: atest PowerGroupTest
Change-Id: I085134ab577d882bbdd17a24b0c0701f9a54c990
parent c68f15b0
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -18,12 +18,16 @@ package com.android.systemui.statusbar.phone

import android.os.Handler
import android.os.PowerManager
import android.platform.test.annotations.RequiresFlagsEnabled
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.server.display.feature.flags.Flags as displayManagerFlags
import com.android.server.power.feature.flags.Flags as powerManagerFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
@@ -36,6 +40,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.GlobalSettings
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -68,6 +73,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
    @Mock private lateinit var statusBarStateController: StatusBarStateControllerImpl
    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
    @Mock private lateinit var powerManager: PowerManager
    @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
    @Mock private lateinit var handler: Handler

    val kosmos = testKosmos()
@@ -88,6 +94,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
                powerManager,
                { shadeLockscreenInteractor },
                { panelExpansionInteractor },
                { displayStateInteractor },
                handler,
            )
        controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
@@ -112,6 +119,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
    fun testAodUiShownIfNotInteractive() {
        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
        `when`(powerManager.isInteractive).thenReturn(false)
        `when`(displayStateInteractor.isDefaultDisplayOff).thenReturn(MutableStateFlow(false))

        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
        controller.startAnimation()
@@ -127,6 +135,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
    fun testAodUiShowNotInvokedIfWakingUp() {
        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
        `when`(powerManager.isInteractive).thenReturn(false)
        `when`(displayStateInteractor.isDefaultDisplayOff).thenReturn(MutableStateFlow(false))

        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
        controller.startAnimation()
@@ -151,6 +160,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
    fun testAodUiNotShownIfInteractive() {
        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
        `when`(powerManager.isInteractive(eq(Display.DEFAULT_DISPLAY))).thenReturn(true)
        `when`(displayStateInteractor.isDefaultDisplayOff).thenReturn(MutableStateFlow(false))

        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
        controller.startAnimation()
@@ -166,6 +176,7 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
        `when`(powerManager.isInteractive()).thenReturn(false)
        `when`(powerManager.isInteractive(eq(Display.DEFAULT_DISPLAY))).thenReturn(false)
        `when`(displayStateInteractor.isDefaultDisplayOff).thenReturn(MutableStateFlow(false))

        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
        controller.startAnimation()
@@ -184,4 +195,18 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
        controller.startAnimation()
        assertFalse(controller.isAnimationPlaying())
    }

    @RequiresFlagsEnabled(
        displayManagerFlags.FLAG_SEPARATE_TIMEOUTS,
        powerManagerFlags.FLAG_SEPARATE_TIMEOUTS_FLICKER,
    )
    @Test
    fun testNoAnimationPlaying_whenDefaultDisplayIsOff() {
        `when`(displayStateInteractor.isDefaultDisplayOff).thenReturn(MutableStateFlow(true))
        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)

        assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
        controller.startAnimation()
        assertFalse(controller.isAnimationPlaying())
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ import kotlinx.coroutines.flow.stateIn
/** Aggregates display state information. */
interface DisplayStateInteractor {
    /** Whether the default display is currently off. */
    val isDefaultDisplayOff: Flow<Boolean>
    val isDefaultDisplayOff: StateFlow<Boolean>

    /** Whether the device is currently in rear display mode. */
    val isInRearDisplayMode: StateFlow<Boolean>
+11 −0
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@ import com.android.app.tracing.namedRunnable
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.server.power.feature.flags.Flags as powerManagerFlags
import com.android.systemui.DejankUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
@@ -69,6 +71,7 @@ constructor(
    private val powerManager: PowerManager,
    private val shadeLockscreenInteractorLazy: Lazy<ShadeLockscreenInteractor>,
    private val panelExpansionInteractorLazy: Lazy<PanelExpansionInteractor>,
    private val displayStateInteractorLazy: Lazy<DisplayStateInteractor>,
    @Main private val handler: Handler,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
    private lateinit var centralSurfaces: CentralSurfaces
@@ -368,6 +371,14 @@ constructor(
            return false
        }

        // If this display is off, skip animation to reduce flickers.
        if (
            powerManagerFlags.separateTimeoutsFlicker() &&
                displayStateInteractorLazy.get().isDefaultDisplayOff.value
        ) {
            return false
        }

        // Otherwise, good to go.
        return true
    }
+2 −5
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.runBlocking
import org.mockito.kotlin.mock

@@ -126,9 +125,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
    override val pendingDisplay: Flow<PendingDisplay?>
        get() = pendingDisplayFlow

    private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
    override val defaultDisplayOff: Flow<Boolean>
        get() = _defaultDisplayOff.asStateFlow()
    override val defaultDisplayOff = MutableStateFlow(false)

    override fun getDisplay(displayId: Int): Display? {
        return displays.value.find { it.displayId == displayId }
@@ -148,7 +145,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
    suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)

    fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {
        _defaultDisplayOff.value = defaultDisplayOff
        this.defaultDisplayOff.value = defaultDisplayOff
    }
}

+10 −4
Original line number Diff line number Diff line
@@ -530,7 +530,7 @@ public class PowerGroup {
    // interactivity state
    private void updateScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
            boolean bootCompleted, boolean screenBrightnessBoostInProgress,
            boolean brightWhenDozing) {
            boolean brightWhenDozing, boolean allAdjacentGroupsAreNonInteractive) {
        final int wakefulness = getWakefulnessLocked();
        final int wakeLockSummary = getWakeLockSummaryLocked();
        int policyReason = Display.STATE_REASON_DEFAULT_POLICY;
@@ -543,7 +543,12 @@ public class PowerGroup {
        } else if (wakefulness == WAKEFULNESS_DOZING) {
            if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
                policy = DisplayPowerRequest.POLICY_DOZE;
            } else if (dozeAfterScreenOff) {
            } else if (dozeAfterScreenOff || (mFeatureFlags.isSeparateTimeoutsFlickerEnabled()
                    && allAdjacentGroupsAreNonInteractive
                    && mGroupId == Display.DEFAULT_DISPLAY_GROUP)) {
                // If we force dozeAfterScreenOff or
                // if we have adjacent groups, but they are all non-interactive now,
                // then set policy to OFF instead to reduce flickers.
                policy = DisplayPowerRequest.POLICY_OFF;
            } else if (brightWhenDozing) {
                policy = DisplayPowerRequest.POLICY_BRIGHT;
@@ -585,9 +590,10 @@ public class PowerGroup {
            PowerSaveState powerSaverState, boolean quiescent,
            boolean dozeAfterScreenOff, boolean bootCompleted,
            boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
            boolean brightWhenDozing) {
            boolean brightWhenDozing, boolean allAdjacentGroupsAreNonInteractive) {
        updateScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
                bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing,
                allAdjacentGroupsAreNonInteractive);
        mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
        mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
        mDisplayPowerRequest.useProximitySensor = useProximitySensor;
Loading