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

Commit 976713c2 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Fix statusbar visibility wrt shade expansion in multiple displays

The statusbar was only considering the state of Scene container (or shade expansion), without caring about whether the shade window was on its own display.

This caused it to be hidden when the shade was expanded in another dispaly.

With this change, we're always showing the statusbar when the shade window is in another display.

Also note that the keyguard is always in the shade window, and once the device is locked it goes back to the default display.

Bug: 362719719
Bug: 397155459
Test: HomeStatusBarViewModelImplTest
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I78323f206acc162f1150cf8c685ffdf87dccfc8b
parent 6ac893b2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ class FakeHomeStatusBarViewModel(
    override val mediaProjectionStopDialogDueToCallEndedState =
        MutableStateFlow(MediaProjectionStopDialogModel.Hidden)

    override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
    override val isHomeStatusBarAllowed = MutableStateFlow(false)

    override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false)

+119 −17
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.fake
@@ -57,6 +58,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
@@ -95,7 +97,6 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -500,9 +501,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneLockscreen_notOccluded_false() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneLockscreen_notOccluded_false() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
@@ -511,9 +513,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneLockscreen_occluded_true() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneLockscreen_occluded_true() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
@@ -522,9 +525,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_overlayBouncer_false() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_overlayBouncer_false() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            kosmos.sceneContainerRepository.showOverlay(Overlays.Bouncer)
@@ -533,9 +537,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneCommunal_false() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneCommunal_false() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Communal)

@@ -543,9 +548,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneShade_false() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneShade_false() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)

@@ -553,9 +559,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneGone_true() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneGone_true() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)

@@ -563,9 +570,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneGoneWithNotificationsShadeOverlay_false() =
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_sceneGoneWithNotificationsShadeOverlay_false() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
            kosmos.sceneContainerRepository.showOverlay(Overlays.NotificationsShade)
@@ -575,12 +583,102 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        }

    @Test
    fun isHomeStatusBarAllowedByScene_sceneGoneWithQuickSettingsShadeOverlay_false() =
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_QsVisibleButInExternalDisplay_defaultStatusBarVisible() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
            kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
            kosmos.fakeShadeDisplaysRepository.setDisplayId(EXTERNAL_DISPLAY)
            runCurrent()

            assertThat(latest).isTrue()
        }

    @Test
    @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_QsVisibleButInExternalDisplay_withFlagOff_defaultStatusBarInvisible() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
            kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
            kosmos.fakeShadeDisplaysRepository.setDisplayId(EXTERNAL_DISPLAY)
            runCurrent()

            // Shade position is ignored.
            assertThat(latest).isFalse()
        }

    @Test
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_qsVisibleInThisDisplay_thisStatusBarInvisible() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
            kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
            kosmos.fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)
            runCurrent()

            assertThat(latest).isFalse()
        }

    @Test
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_qsExpandedOnDefaultDisplay_statusBarInAnotherDisplay_visible() =
        kosmos.runTest {
            val underTest = homeStatusBarViewModelFactory(EXTERNAL_DISPLAY)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
            kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
            runCurrent()

            assertThat(latest).isTrue()
        }

    @Test
    @EnableSceneContainer
    fun isHomeStatusBarAllowed_onDefaultDisplayLockscreen_invisible() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            runCurrent()

            assertThat(latest).isFalse()
        }

    @Test
    @EnableSceneContainer
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun isHomeStatusBarAllowed_onExternalDispalyWithLocksceren_invisible() =
        kosmos.runTest {
            val underTest = homeStatusBarViewModelFactory(EXTERNAL_DISPLAY)
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            runCurrent()

            assertThat(latest).isFalse()
        }

    @Test
    @DisableSceneContainer
    fun isHomeStatusBarAllowed_legacy_onDefaultDisplayLockscreen_invisible() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.isHomeStatusBarAllowed)

            kosmos.fakeKeyguardTransitionRepository.transitionTo(
                KeyguardState.GONE,
                KeyguardState.LOCKSCREEN,
            )

            runCurrent()

            assertThat(latest).isFalse()
@@ -1289,4 +1387,8 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
            testScope = testScope,
        )
    }

    private companion object {
        const val EXTERNAL_DISPLAY = 1
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -52,7 +52,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull


/** Handles Shade window display change when [ShadeDisplaysRepository.displayId] changes. */
@SysUISingleton
class ShadeDisplaysInteractor
@@ -75,6 +74,9 @@ constructor(
    private val hasActiveNotifications: Boolean
        get() = activeNotificationsInteractor.areAnyNotificationsPresentValue

    /** Current display id of the shade window. */
    val displayId: StateFlow<Int> = shadePositionRepository.displayId

    override fun start() {
        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
        listenForWindowContextConfigChanges()
+2 −2
Original line number Diff line number Diff line
@@ -249,7 +249,7 @@ constructor(
                if (SceneContainerFlag.isEnabled) {
                    listener?.let { listener ->
                        launch {
                            viewModel.isHomeStatusBarAllowedByScene.collect {
                            viewModel.isHomeStatusBarAllowed.collect {
                                listener.onIsHomeStatusBarAllowedBySceneChanged(it)
                            }
                        }
@@ -495,7 +495,7 @@ interface StatusBarVisibilityChangeListener {

    /**
     * Called when the scene state has changed such that the home status bar is newly allowed or no
     * longer allowed. See [HomeStatusBarViewModel.isHomeStatusBarAllowedByScene].
     * longer allowed. See [HomeStatusBarViewModel.isHomeStatusBarAllowed].
     */
    fun onIsHomeStatusBarAllowedBySceneChanged(isHomeStatusBarAllowedByScene: Boolean)
}
+57 −16
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel

import android.annotation.ColorInt
import android.graphics.Rect
import android.view.Display
import android.view.View
import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
@@ -41,7 +42,9 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
@@ -72,6 +75,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
@@ -141,13 +145,12 @@ interface HomeStatusBarViewModel : Activatable {
    val popupChips: List<PopupChipModel.Shown>

    /**
     * True if the current scene can show the home status bar (aka this status bar), and false if
     * the current scene should never show the home status bar.
     * True if the status bar should be visible.
     *
     * TODO(b/364360986): Once the is<SomeChildView>Visible flows are fully enabled, we shouldn't
     *   need this flow anymore.
     */
    val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
    val isHomeStatusBarAllowed: StateFlow<Boolean>

    /** True if the home status bar is showing, and there is no HUN happening */
    val canShowOngoingActivityChips: Flow<Boolean>
@@ -221,6 +224,7 @@ constructor(
    statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
    @Background bgScope: CoroutineScope,
    @Background bgDispatcher: CoroutineDispatcher,
    shadeDisplaysInteractor: Provider<ShadeDisplaysInteractor>,
) : HomeStatusBarViewModel, ExclusiveActivatable() {

    private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
@@ -259,12 +263,44 @@ constructor(
    override val popupChips
        get() = statusBarPopupChips.shownPopupChips

    override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> =
    /**
     * Whether the display of this statusbar has the shade window (that is hosting shade container
     * and lockscreen, among other things).
     */
    private val isShadeWindowOnThisDisplay =
        if (ShadeWindowGoesAround.isEnabled) {
            shadeDisplaysInteractor.get().displayId.map { shadeDisplayId ->
                thisDisplayId == shadeDisplayId
            }
        } else {
            // Shade doesn't move anywhere, it is always on the default display.
            flowOf(thisDisplayId == Display.DEFAULT_DISPLAY)
        }

    private val isShadeVisibleOnAnyDisplay =
        if (SceneContainerFlag.isEnabled) {
            sceneInteractor.currentOverlays.map { currentOverlays ->
                (Overlays.NotificationsShade in currentOverlays ||
                    Overlays.QuickSettingsShade in currentOverlays)
            }
        } else {
            shadeInteractor.isAnyFullyExpanded
        }

    private val isShadeVisibleOnThisDisplay: Flow<Boolean> =
        combine(isShadeWindowOnThisDisplay, isShadeVisibleOnAnyDisplay) {
            hasShade,
            isShadeVisibleOnAnyDisplay ->
            hasShade && isShadeVisibleOnAnyDisplay
        }

    private val isHomeStatusBarAllowedByScene: Flow<Boolean> =
        combine(
                sceneInteractor.currentScene,
                sceneInteractor.currentOverlays,
                isShadeVisibleOnThisDisplay,
                sceneContainerOcclusionInteractor.invisibleDueToOcclusion,
            ) { currentScene, currentOverlays, isOccluded ->
            ) { currentScene, isShadeVisible, isOccluded ->

                // All scenes have their own status bars, so we should only show the home status bar
                // if we're not in a scene. There are two exceptions:
                // 1) The shade (notifications or quick settings) is shown, because it has its own
@@ -272,9 +308,7 @@ constructor(
                // 2) If the scene is occluded, then the occluding app needs to show the status bar.
                // (Fullscreen apps actually won't show the status bar but that's handled with the
                // rest of our fullscreen app logic, which lives elsewhere.)
                (currentScene == Scenes.Gone &&
                    Overlays.NotificationsShade !in currentOverlays &&
                    Overlays.QuickSettingsShade !in currentOverlays) || isOccluded
                (currentScene == Scenes.Gone && !isShadeVisible) || isOccluded
            }
            .distinctUntilChanged()
            .logDiffsForTable(
@@ -282,7 +316,6 @@ constructor(
                columnName = COL_ALLOWED_BY_SCENE,
                initialValue = false,
            )
            .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)

    override val areNotificationsLightsOut: Flow<Boolean> =
        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
@@ -331,21 +364,29 @@ constructor(
     * if we shouldn't be showing any part of the home status bar.
     */
    private val isHomeScreenStatusBarAllowedLegacy: Flow<Boolean> =
        combine(
            keyguardTransitionInteractor.currentKeyguardState,
            shadeInteractor.isAnyFullyExpanded,
        ) { currentKeyguardState, isShadeExpanded ->
            (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) && !isShadeExpanded
        combine(keyguardTransitionInteractor.currentKeyguardState, isShadeVisibleOnThisDisplay) {
            currentKeyguardState,
            isShadeVisibleOnThisDisplay ->
            (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) &&
                !isShadeVisibleOnThisDisplay
            // TODO(b/364360986): Add edge cases, like secure camera launch.
        }

    private val isHomeStatusBarAllowed: Flow<Boolean> =
    // "Compat" to cover both legacy and Scene container case in one flow.
    private val isHomeStatusBarAllowedCompat =
        if (SceneContainerFlag.isEnabled) {
            isHomeStatusBarAllowedByScene
        } else {
            isHomeScreenStatusBarAllowedLegacy
        }

    override val isHomeStatusBarAllowed =
        isHomeStatusBarAllowedCompat.stateIn(
            bgScope,
            SharingStarted.WhileSubscribed(),
            initialValue = false,
        )

    private val shouldHomeStatusBarBeVisible =
        combine(
                isHomeStatusBarAllowed,
Loading