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

Commit 2ab78863 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Force SHOW_WALLPAPER flag when display switching

When unfolding from 'screen off' SHOW_WALLPAPER flag is not
set immediately on SystemUI side as dozing state/keyguard showing
state are updated asynchronously.

To ensure that the wallpaper is part of the unfold Shell
transition, at it is rendered in sync with the rest of
the windows before we turn on the inner screen, this change adds
forcing of SHOW_WALLPAPER flag for any display switching
transition.

Bug: 395612286
Flag: com.android.window.flags.ensure_wallpaper_drawn_on_display_switch
Test: atest NotificationShadeWindowControllerImplTest
Test: atest PendingDisplayChangeControllerTest
Change-Id: I24cf9faa7256e9ff7902fbd92358280118021f24
parent 55740f38
Loading
Loading
Loading
Loading
+11 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.policy.IKeyguardService.SCREEN_TURNING_ON_REASON_UNKNOWN
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.display.PendingDisplayChangeController
import com.android.systemui.unfold.FoldAodAnimationController
import com.android.systemui.unfold.FullscreenLightRevealAnimation
import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -57,6 +58,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
    private lateinit var fullscreenLightRevealAnimation: FullscreenLightRevealAnimation
    @Mock
    private lateinit var fullScreenLightRevealAnimations: Set<FullscreenLightRevealAnimation>
    @Mock
    private lateinit var pendingDisplayChangeController: PendingDisplayChangeController
    @Captor
    private lateinit var readyCaptor: ArgumentCaptor<Runnable>

@@ -78,7 +81,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {

        screenOnCoordinator = ScreenOnCoordinator(
            Optional.of(unfoldComponent),
            testHandler
            testHandler,
            pendingDisplayChangeController,
        )
    }

@@ -128,7 +132,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
        // Recreate with empty unfoldComponent
        screenOnCoordinator = ScreenOnCoordinator(
            Optional.empty(),
            testHandler
            testHandler,
            pendingDisplayChangeController,
        )
        screenOnCoordinator.onScreenTurningOn(reason = SCREEN_TURNING_ON_REASON_UNKNOWN, runnable)
        waitHandlerIdle()
@@ -142,7 +147,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
        // Recreate with empty unfoldComponent
        screenOnCoordinator = ScreenOnCoordinator(
                Optional.empty(),
                testHandler
                testHandler,
                pendingDisplayChangeController,
        )
        screenOnCoordinator.onScreenTurningOn(reason = SCREEN_TURNING_ON_REASON_UNKNOWN, runnable)

@@ -156,7 +162,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
        // Recreate with empty unfoldComponent
        screenOnCoordinator = ScreenOnCoordinator(
                Optional.empty(),
                testHandler
                testHandler,
                pendingDisplayChangeController,
        )
        screenOnCoordinator.onScreenTurningOn(reason = SCREEN_TURNING_ON_REASON_UNKNOWN, runnable)
        // No need to wait for the handler to be idle, as it shouldn't be used
+26 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.shade;

import static android.service.dreams.Flags.FLAG_DREAMS_V2;
import static com.android.window.flags.Flags.FLAG_ENSURE_WALLPAPER_DRAWN_ON_DISPLAY_SWITCH;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -274,6 +275,31 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
        assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
    }

    @EnableFlags(FLAG_ENSURE_WALLPAPER_DRAWN_ON_DISPLAY_SWITCH)
    @Test
    public void attach_pendingDisplayChange_wallpaperVisible() {
        mNotificationShadeWindowController.attach();
        clearInvocations(mWindowManager);

        mNotificationShadeWindowController.setPendingDisplayChange(true);

        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
        assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
    }

    @EnableFlags(FLAG_ENSURE_WALLPAPER_DRAWN_ON_DISPLAY_SWITCH)
    @Test
    public void attach_pendingDisplayChangeFinished_wallpaperNotVisible() {
        mNotificationShadeWindowController.attach();
        mNotificationShadeWindowController.setPendingDisplayChange(true);
        clearInvocations(mWindowManager);

        mNotificationShadeWindowController.setPendingDisplayChange(false);

        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
        assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isFalse();
    }

    @Test
    public void setBouncerShowing_isFocusable_whenNeedsInput() {
        mNotificationShadeWindowController.setKeyguardNeedsInput(true);
+173 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.shade.display

import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.policy.IKeyguardService.SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH
import com.android.internal.policy.IKeyguardService.SCREEN_TURNING_ON_REASON_UNKNOWN
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.verifyCurrent
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.testKosmos
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(com.android.window.flags.Flags.FLAG_ENSURE_WALLPAPER_DRAWN_ON_DISPLAY_SWITCH)
class PendingDisplayChangeControllerTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val powerRepository = kosmos.fakePowerRepository

    private lateinit var underTest: PendingDisplayChangeController

    @Test
    fun onScreenTurningOnWithDisplaySwitching_notifiesShadeWindowController() =
        kosmos.runTest {
            createAndStartController()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH, mock<Runnable>())

            verifyCurrent(notificationShadeWindowController).setPendingDisplayChange(true)
        }

    @Test
    fun onScreenTurningOnWithDisplaySwitching_notifiesScreenTurningOnComplete() =
        kosmos.runTest {
            createAndStartController()
            val onScreenTurningOnComplete = mock<Runnable>()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH, onScreenTurningOnComplete)

            verifyCurrent(onScreenTurningOnComplete).run()
        }

    @Test
    fun onScreenTurningWithoutDisplaySwitching_notifiesScreenTurningOnComplete() =
        kosmos.runTest {
            createAndStartController()
            val onScreenTurningOnComplete = mock<Runnable>()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_UNKNOWN, onScreenTurningOnComplete)

            verifyCurrent(onScreenTurningOnComplete).run()
        }

    @Test
    fun onScreenTurningWithoutDisplaySwitching_doesNotSetPendingScreenState() =
        kosmos.runTest {
            createAndStartController()
            val onScreenTurningOnComplete = mock<Runnable>()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_UNKNOWN, onScreenTurningOnComplete)

            runCurrent()

            verify(notificationShadeWindowController, never()).setPendingDisplayChange(anyBoolean())
        }

    @Test
    fun onScreenTurningOnWithDisplaySwitching_screenTurnedOn_resetsPendingDisplayChange() =
        kosmos.runTest {
            createAndStartController()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH, mock<Runnable>())
            runCurrent()
            clearInvocations(notificationShadeWindowController)

            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)

            verifyCurrent(notificationShadeWindowController).setPendingDisplayChange(false)
        }

    @Test
    fun onScreenTurningOnWithDisplaySwitching_screenTurningOnTimeout_resetsPendingDisplayChange() =
        kosmos.runTest {
            createAndStartController()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH, mock<Runnable>())
            runCurrent()
            clearInvocations(notificationShadeWindowController)

            testScope.advanceTimeBy(15.seconds)

            verify(notificationShadeWindowController).setPendingDisplayChange(false)
        }

    @Test
    fun onScreenTurningOnWithDisplaySwitching_beforeTurningOnTimeout_noPendingDisplayChange() =
        kosmos.runTest {
            createAndStartController()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH, mock<Runnable>())
            runCurrent()
            clearInvocations(notificationShadeWindowController)

            testScope.advanceTimeBy(100.milliseconds)

            verifyNoInteractions(notificationShadeWindowController)
        }

    @Test
    fun onScreenTurningOnWithDisplaySwitching_previousOneIsStillInProgress_processesBoth() =
        kosmos.runTest {
            createAndStartController()
            val onScreenTurningOnComplete1 = mock<Runnable>()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH,
                onScreenTurningOnComplete1)
            runCurrent()

            val onScreenTurningOnComplete2 = mock<Runnable>()
            underTest.onScreenTurningOn(SCREEN_TURNING_ON_REASON_DISPLAY_SWITCH,
                onScreenTurningOnComplete2)
            runCurrent()

            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)

            verifyCurrent(onScreenTurningOnComplete1).run()
            verifyCurrent(onScreenTurningOnComplete2).run()

            inOrder(notificationShadeWindowController) {
                verify(notificationShadeWindowController).setPendingDisplayChange(true)
                verify(notificationShadeWindowController).setPendingDisplayChange(false)
                verify(notificationShadeWindowController).setPendingDisplayChange(true)
                verify(notificationShadeWindowController).setPendingDisplayChange(false)
            }
        }

    private fun Kosmos.createAndStartController() {
        underTest = PendingDisplayChangeController(
            testScope.backgroundScope,
            testScope.backgroundScope,
            { notificationShadeWindowController },
            powerInteractor
        ).also { it.start() }
        runCurrent()
    }
}
 No newline at end of file
+8 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import android.os.Trace
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shade.display.EnsureWallpaperDrawnOnDisplaySwitch
import com.android.systemui.shade.display.PendingDisplayChangeController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.util.concurrency.PendingTasksContainer
import com.android.systemui.util.kotlin.getOrNull
@@ -40,6 +42,7 @@ class ScreenOnCoordinator
constructor(
    unfoldComponent: Optional<SysUIUnfoldComponent>,
    @Main private val mainHandler: Handler,
    private val pendingDisplayChangeController: PendingDisplayChangeController
) {

    private val foldAodAnimationController =
@@ -63,6 +66,11 @@ constructor(
            it.onScreenTurningOn(pendingTasks.registerTask(it::class.java.simpleName))
        }

        if (EnsureWallpaperDrawnOnDisplaySwitch.isEnabled) {
            pendingDisplayChangeController.onScreenTurningOn(reason,
                pendingTasks.registerTask("pending-display-change-controller"));
        }

        pendingTasks.onTasksComplete {
            if (Flags.enableBackgroundKeyguardOndrawnCallback()) {
                // called by whatever thread completes the last task registered.
+15 −1
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.display.EnsureWallpaperDrawnOnDisplaySwitch;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -402,7 +403,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
        final boolean keyguardOrAod = state.keyguardShowing
                || (state.dozing && mDozeParameters.getAlwaysOn());
        if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()
                || (EnsureWallpaperDrawnOnDisplaySwitch.isEnabled() && state.pendingDisplayChange)
        ) {
            // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
            // solid backdrop. Also, show it if we are currently animating between the
            // keyguard and the surface behind the keyguard - we want to use the wallpaper as a
@@ -655,6 +658,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
                state.qsExpanded,
                state.headsUpNotificationShowing,
                state.lightRevealScrimOpaque,
                state.pendingDisplayChange,
                state.isSwitchingUsers,
                state.forceWindowCollapsed,
                state.forceDozeBrightness,
@@ -896,6 +900,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
        apply(mCurrentState);
    }

    @Override
    public void setPendingDisplayChange(boolean pendingDisplayChange) {
        if (EnsureWallpaperDrawnOnDisplaySwitch.isUnexpectedlyInLegacyMode()
                || mCurrentState.pendingDisplayChange == pendingDisplayChange) {
            return;
        }
        mCurrentState.pendingDisplayChange = pendingDisplayChange;
        apply(mCurrentState);
    }

    /**
     * @param state The {@link StatusBarStateController} of the status bar.
     */
Loading