diff options
22 files changed, 652 insertions, 92 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt new file mode 100644 index 000000000000..24bd8adaa45a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt @@ -0,0 +1,105 @@ +/* + * 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.model + +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.dumpManager +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.never +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SysUIStateOverrideTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val defaultState = kosmos.sysUiState + private val callbackOnOverride = mock<SysUiState.SysUiStateCallback>() + private val dumpManager = kosmos.dumpManager + + private val underTest = kosmos.sysUiStateOverrideFactory.invoke(DISPLAY_1) + + @Before + fun setup() { + underTest.start() + underTest.addCallback(callbackOnOverride) + reset(callbackOnOverride) + } + + @Test + fun setFlag_setOnDefaultState_propagatedToOverride() { + defaultState.setFlag(FLAG_1, true).commitUpdate() + + verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY) + verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, DISPLAY_1) + } + + @Test + fun setFlag_onOverride_overridesDefaultOnes() { + defaultState.setFlag(FLAG_1, false).setFlag(FLAG_2, true).commitUpdate() + underTest.setFlag(FLAG_1, true).setFlag(FLAG_2, false).commitUpdate() + + assertThat(underTest.isFlagEnabled(FLAG_1)).isTrue() + assertThat(underTest.isFlagEnabled(FLAG_2)).isFalse() + + assertThat(defaultState.isFlagEnabled(FLAG_1)).isFalse() + assertThat(defaultState.isFlagEnabled(FLAG_2)).isTrue() + } + + @Test + fun destroy_callbacksForDefaultStateNotReceivedAnymore() { + defaultState.setFlag(FLAG_1, true).commitUpdate() + + verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY) + + reset(callbackOnOverride) + underTest.destroy() + defaultState.setFlag(FLAG_1, false).commitUpdate() + + verify(callbackOnOverride, never()).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY) + } + + @Test + fun init_registersWithDumpManager() { + verify(dumpManager).registerNormalDumpable(any(), eq(underTest)) + } + + @Test + fun destroy_unregistersWithDumpManager() { + underTest.destroy() + + verify(dumpManager).unregisterDumpable(ArgumentMatchers.anyString()) + } + + private companion object { + const val DISPLAY_1 = 1 + const val FLAG_1 = 1L + const val FLAG_2 = 2L + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java index f6de6295212b..5bb7f36e4187 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java @@ -140,6 +140,8 @@ public class SysUiStateTest extends SysuiTestCase { @Test public void init_registersWithDumpManager() { + mFlagsContainer.start(); + verify(mDumpManager).registerNormalDumpable(any(), eq(mFlagsContainer)); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt index 9498daaf0b07..c635c7ff69a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt @@ -67,7 +67,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { fun policy_changing_propagatedFromTheLatestPolicy() = testScope.runTest { val underTest = createUnderTest() - val displayIds by collectValues(underTest.displayId) + val displayIds by collectValues(underTest.pendingDisplayId) assertThat(displayIds).containsExactly(0) @@ -98,7 +98,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { DEVELOPMENT_SHADE_DISPLAY_AWARENESS, FakeShadeDisplayPolicy.name, ) - val displayId by collectLastValue(underTest.displayId) + val displayId by collectLastValue(underTest.pendingDisplayId) displayRepository.addDisplay(displayId = 1) @@ -161,7 +161,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { FakeShadeDisplayPolicy.name, ) - val displayId by collectLastValue(underTest.displayId) + val displayId by collectLastValue(underTest.pendingDisplayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) FakeShadeDisplayPolicy.setDisplayId(2) @@ -176,4 +176,17 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { assertThat(displayId).isEqualTo(2) } + + @Test + fun onDisplayChangedSucceeded_displayIdChanges() = + testScope.runTest { + val underTest = createUnderTest() + val displayId by collectLastValue(underTest.displayId) + + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + underTest.onDisplayChangedSucceeded(1) + + assertThat(displayId).isEqualTo(1) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt index fd6bc98b006c..f51d41bbc80b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt @@ -69,7 +69,7 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() { @Test fun commandShadeDisplayOverride_resetsDisplayId() = testScope.runTest { - val displayId by collectLastValue(shadeDisplaysRepository.displayId) + val displayId by collectLastValue(shadeDisplaysRepository.pendingDisplayId) assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) val newDisplayId = 2 @@ -87,7 +87,7 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() { @Test fun commandShadeDisplayOverride_anyExternalDisplay_notOnDefaultAnymore() = testScope.runTest { - val displayId by collectLastValue(shadeDisplaysRepository.displayId) + val displayId by collectLastValue(shadeDisplaysRepository.pendingDisplayId) assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) val newDisplayId = 2 displayRepository.addDisplay(displayId = newDisplayId) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt index 0ad60b617194..03f546b09faf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt @@ -22,7 +22,6 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.data.repository.configurationRepository import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -82,7 +81,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInCorrectPosition_notAddedOrRemoved() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(0) + positionRepository.setPendingDisplayId(0) underTest.start() @@ -93,18 +92,19 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInWrongPosition_changes() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) underTest.start() verify(shadeContext).reparentToDisplay(eq(1)) + assertThat(positionRepository.displayId.value).isEqualTo(1) } @Test fun start_shadeInWrongPosition_logsStartToLatencyTracker() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) underTest.start() @@ -115,7 +115,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInWrongPosition_someNotificationsVisible_hiddenThenShown() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) activeNotificationRepository.setActiveNotifs(1) underTest.start() @@ -129,7 +129,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInWrongPosition_someNotificationsVisible_waitsForInflationsBeforeShowingNssl() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) activeNotificationRepository.setActiveNotifs(1) val endRebinding = notificationRebindingTracker.trackRebinding("test") @@ -155,7 +155,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInWrongPosition_noNotifications_nsslNotHidden() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) activeNotificationRepository.setActiveNotifs(0) underTest.start() @@ -170,7 +170,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { fun start_shadeInWrongPosition_waitsUntilMovedToDisplayReceived() = testScope.runTest { whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + positionRepository.setPendingDisplayId(1) activeNotificationRepository.setActiveNotifs(1) underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt index a083e59fe263..91b3896332f5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt @@ -66,7 +66,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) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index 27aa4bab3deb..ee5979c6ed9f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -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 @@ -97,7 +99,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 @@ -502,9 +503,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) @@ -513,9 +515,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) @@ -524,9 +527,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) @@ -535,9 +539,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) @@ -545,9 +550,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) @@ -555,9 +561,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) @@ -565,9 +572,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) @@ -577,14 +585,104 @@ 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.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 latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + 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() } @@ -1560,4 +1658,8 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope = testScope, ) } + + private companion object { + const val EXTERNAL_DISPLAY = 1 + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt index 13f6bba01135..ba7e17bdd7a5 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt @@ -23,6 +23,7 @@ import androidx.annotation.ColorInt import androidx.annotation.DimenRes import androidx.annotation.LayoutRes import com.android.settingslib.Utils +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationState import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged import com.android.systemui.statusbar.policy.onThemeChanged @@ -78,7 +79,7 @@ class ConfigurationStateImpl constructor( @Assisted private val configurationController: ConfigurationController, @Assisted private val context: Context, -) : ConfigurationState { +) : ConfigurationState, StatusBarConfigurationState { private val layoutInflater = LayoutInflater.from(context) @@ -147,7 +148,7 @@ constructor( */ fun create( context: Context, - configurationController: ConfigurationController + configurationController: ConfigurationController, ): ConfigurationStateImpl } } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index 36d3eb51283a..3458c9549665 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -130,6 +130,7 @@ constructor( displayRepository.displayIds.collectLatest { displayIds -> val toRemove = perDisplayInstances.keys - displayIds toRemove.forEach { displayId -> + Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.") perDisplayInstances.remove(displayId)?.let { instance -> (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance( instance @@ -147,6 +148,7 @@ constructor( // If it doesn't exist, create it and put it in the map. return perDisplayInstances.computeIfAbsent(displayId) { key -> + Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.") val instance = traceSection({ "creating instance of $debugName for displayId=$key" }) { instanceProvider.createInstance(key) diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt new file mode 100644 index 000000000000..89e06e226997 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt @@ -0,0 +1,83 @@ +/* + * 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.model + +import android.view.Display +import com.android.systemui.dump.DumpManager +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * This class is used to provide per-display overrides for only certain flags. + * + * While some of the [SystemUiStateFlags] are per display (e.g. shade expansion, dialog visible), + * some of them are device specific (e.g. whether it's awake or not). A [SysUIStateOverride] is + * created for each display that is not [Display.DEFAULT_DISPLAY], and if some flags are set on it, + * they will override whatever the default display state had in those. + */ +class SysUIStateOverride +@AssistedInject +constructor( + @Assisted override val displayId: Int, + private val sceneContainerPlugin: SceneContainerPlugin?, + dumpManager: DumpManager, + private val defaultDisplayState: SysUiState, + private val stateDispatcher: SysUIStateDispatcher, +) : SysUiStateImpl(displayId, sceneContainerPlugin, dumpManager, stateDispatcher) { + + private val override = StateChange() + private var lastSentFlags = defaultDisplayState.flags + + private val defaultFlagsChangedCallback = { _: Long, otherDisplayId: Int -> + if (otherDisplayId == Display.DEFAULT_DISPLAY) { + commitUpdate() + } + } + + override fun start() { + super.start() + stateDispatcher.registerListener(defaultFlagsChangedCallback) + } + + override fun destroy() { + super.destroy() + stateDispatcher.unregisterListener(defaultFlagsChangedCallback) + } + + override fun commitUpdate() { + if (flags != lastSentFlags) { + stateDispatcher.dispatchSysUIStateChange(flags, displayId) + lastSentFlags = flags + } + } + + override val flags: Long + get() = override.applyTo(defaultDisplayState.flags) + + override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState { + val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin) + override.setFlag(flag, toSet) + return this + } + + @AssistedFactory + interface Factory { + fun create(displayId: Int): SysUIStateOverride + } +} diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt index 663355941613..e99ee7ddb919 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt @@ -16,6 +16,7 @@ package com.android.systemui.model import android.util.Log +import android.view.Display import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown @@ -74,6 +75,9 @@ interface SysUiState : Dumpable { */ fun destroy() + /** Initializes the state after construction. */ + fun start() + /** The display ID this instances is associated with */ val displayId: Int @@ -84,7 +88,7 @@ interface SysUiState : Dumpable { private const val TAG = "SysUIState" -class SysUiStateImpl +open class SysUiStateImpl @AssistedInject constructor( @Assisted override val displayId: Int, @@ -93,9 +97,10 @@ constructor( private val stateDispatcher: SysUIStateDispatcher, ) : SysUiState { - private val debugName = "SysUiStateImpl-ForDisplay=$displayId" + private val debugName + get() = "SysUiStateImpl-ForDisplay=$displayId" - init { + override fun start() { dumpManager.registerNormalDumpable(debugName, this) } @@ -222,10 +227,19 @@ fun flagWithOptionalOverrides( /** Creates and destroy instances of [SysUiState] */ @SysUISingleton -class SysUIStateInstanceProvider @Inject constructor(private val factory: SysUiStateImpl.Factory) : - PerDisplayInstanceProviderWithTeardown<SysUiState> { +class SysUIStateInstanceProvider +@Inject +constructor( + private val factory: SysUiStateImpl.Factory, + private val overrideFactory: SysUIStateOverride.Factory, +) : PerDisplayInstanceProviderWithTeardown<SysUiState> { override fun createInstance(displayId: Int): SysUiState { - return factory.create(displayId) + return if (displayId == Display.DEFAULT_DISPLAY) { + factory.create(displayId) + } else { + overrideFactory.create(displayId) + } + .apply { start() } } override fun destroyInstance(instance: SysUiState) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index 96b224fbd4f3..cd224735cc62 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl import com.android.systemui.shade.display.ShadeDisplayPolicyModule @@ -205,7 +206,18 @@ object ShadeDisplayAwareModule { @SysUISingleton @Provides - fun provideShadePositionRepository(impl: ShadeDisplaysRepositoryImpl): ShadeDisplaysRepository { + fun provideShadePositionRepository( + impl: MutableShadeDisplaysRepository + ): ShadeDisplaysRepository { + ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() + return impl + } + + @SysUISingleton + @Provides + fun provideMutableShadePositionRepository( + impl: ShadeDisplaysRepositoryImpl + ): MutableShadeDisplaysRepository { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() return impl } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt index 3513334f2a5c..e18ed83a1e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt @@ -22,16 +22,28 @@ import com.android.systemui.shade.display.ShadeDisplayPolicy import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class FakeShadeDisplayRepository : ShadeDisplaysRepository { +class FakeShadeDisplayRepository : MutableShadeDisplaysRepository { private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY) + private val _pendingDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY) fun setDisplayId(displayId: Int) { _displayId.value = displayId } + fun setPendingDisplayId(displayId: Int) { + _pendingDisplayId.value = displayId + } + + override fun onDisplayChangedSucceeded(displayId: Int) { + setDisplayId(displayId) + } + override val displayId: StateFlow<Int> get() = _displayId + override val pendingDisplayId: StateFlow<Int> + get() = _pendingDisplayId + override val currentPolicy: ShadeDisplayPolicy get() = FakeShadeDisplayPolicy } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt index 2a14ca44386d..7117a23bfb77 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt @@ -29,6 +29,7 @@ import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -40,10 +41,28 @@ import kotlinx.coroutines.flow.stateIn /** Source of truth for the display currently holding the shade. */ interface ShadeDisplaysRepository { - /** ID of the display which currently hosts the shade */ + /** ID of the display which currently hosts the shade. */ val displayId: StateFlow<Int> /** The current policy set. */ val currentPolicy: ShadeDisplayPolicy + + /** + * Id of the display that should host the shade. + * + * If this differs from [displayId], it means there is a shade movement in progress. Classes + * that rely on the shade being already moved (and its context/resources updated) should rely on + * [displayId]. Classes that need to do work associated with the shade move, should listen at + * this. + */ + val pendingDisplayId: StateFlow<Int> +} + +/** Provides a way to set whether the display changed succeeded. */ +interface MutableShadeDisplaysRepository : ShadeDisplaysRepository { + /** + * To be called when the shade changed window, and its resources have been completely updated. + */ + fun onDisplayChangedSucceeded(displayId: Int) } /** @@ -63,7 +82,7 @@ constructor( @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean, keyguardRepository: KeyguardRepository, displayRepository: DisplayRepository, -) : ShadeDisplaysRepository { +) : MutableShadeDisplaysRepository { private val policy: StateFlow<ShadeDisplayPolicy> = globalSettings @@ -105,10 +124,16 @@ constructor( override val currentPolicy: ShadeDisplayPolicy get() = policy.value - override val displayId: StateFlow<Int> = + override val pendingDisplayId: StateFlow<Int> = keyguardAwareDisplayPolicy.stateIn( bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY, ) + private val _committedDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY) + override val displayId: StateFlow<Int> = _committedDisplayId + + override fun onDisplayChangedSucceeded(displayId: Int) { + _committedDisplayId.value = displayId + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt index 930b1cb1905c..0e0f58dc8d0e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo import com.android.systemui.shade.ShadeTraceLogger.t import com.android.systemui.shade.ShadeTraceLogger.traceReparenting +import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround @@ -44,6 +45,7 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -55,7 +57,7 @@ import kotlinx.coroutines.withTimeoutOrNull class ShadeDisplaysInteractor @Inject constructor( - private val shadePositionRepository: ShadeDisplaysRepository, + private val shadePositionRepository: MutableShadeDisplaysRepository, @ShadeDisplayAware private val shadeContext: WindowContext, @ShadeDisplayAware private val configurationRepository: ConfigurationRepository, @Background private val bgScope: CoroutineScope, @@ -72,11 +74,14 @@ 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() bgScope.launchTraced(TAG) { - shadePositionRepository.displayId.collectLatest { displayId -> + shadePositionRepository.pendingDisplayId.collectLatest { displayId -> moveShadeWindowTo(displayId) } } @@ -119,6 +124,7 @@ constructor( reparentToDisplayId(id = destinationId) } checkContextDisplayMatchesExpected(destinationId) + shadePositionRepository.onDisplayChangedSucceeded(destinationId) } } } catch (e: IllegalStateException) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt index 27d815190d85..5e7e1793ab35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositor import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationStateModule import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule @@ -34,6 +35,7 @@ import dagger.Module LightBarControllerStoreModule::class, RemoteInputRepositoryModule::class, StatusBarConfigurationControllerModule::class, + StatusBarConfigurationStateModule::class, StatusBarContentInsetsProviderStoreModule::class, StatusBarModeRepositoryModule::class, StatusBarPhoneDataLayerModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt new file mode 100644 index 000000000000..4e6c5ddc3066 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt @@ -0,0 +1,120 @@ +/* + * 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.statusbar.data.repository + +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR +import com.android.systemui.CoreStartable +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.common.ui.ConfigurationStateImpl +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope + +/** Status bar specific interface to disambiguate from the global [ConfigurationState]. */ +interface StatusBarConfigurationState : ConfigurationState + +/** Provides per display instances of [ConfigurationState], specifically for the Status Bar. */ +interface StatusBarConfigurationStateStore : PerDisplayStore<StatusBarConfigurationState> + +@SysUISingleton +class MultiDisplayStatusBarConfigurationStateStore +@Inject +constructor( + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, + private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, + private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, + private val factory: ConfigurationStateImpl.Factory, +) : + StatusBarConfigurationStateStore, + PerDisplayStoreImpl<StatusBarConfigurationState>( + backgroundApplicationScope, + displayRepository, + ) { + + init { + StatusBarConnectedDisplays.unsafeAssertInNewMode() + } + + override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationState? { + val displayWindowProperties = + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null + val configController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null + return factory.create(displayWindowProperties.context, configController) + } + + override val instanceClass = StatusBarConfigurationState::class.java +} + +@SysUISingleton +class SingleDisplayStatusBarConfigurationStateStore +@Inject +constructor(@Main globalConfigState: ConfigurationState) : + StatusBarConfigurationStateStore, + PerDisplayStore<StatusBarConfigurationState> by SingleDisplayStore( + globalConfigState as StatusBarConfigurationState + ) { + + init { + StatusBarConnectedDisplays.assertInLegacyMode() + } +} + +@Module +object StatusBarConfigurationStateModule { + + @Provides + @SysUISingleton + fun store( + singleDisplayLazy: Lazy<SingleDisplayStatusBarConfigurationStateStore>, + multiDisplayLazy: Lazy<MultiDisplayStatusBarConfigurationStateStore>, + ): StatusBarConfigurationStateStore { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + singleDisplayLazy.get() + } + } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(StatusBarConfigurationStateStore::class) + fun storeAsCoreStartable( + multiDisplayLazy: Lazy<MultiDisplayStatusBarConfigurationStateStore> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + CoreStartable.NOP + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt index aa81ebf22ac6..30aec8dabcaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -18,11 +18,10 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.view.Display import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationStateStore import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel @@ -37,7 +36,8 @@ class NotificationIconContainerStatusBarViewBinder @Inject constructor( private val viewModel: NotificationIconContainerStatusBarViewModel, - @ShadeDisplayAware private val configuration: ConfigurationState, + private val configurationStore: StatusBarConfigurationStateStore, + private val defaultConfigurationState: ConfigurationState, private val systemBarUtilsState: SystemBarUtilsState, private val failureTracker: StatusBarIconViewBindingFailureTracker, private val defaultDisplayViewStore: StatusBarNotificationIconViewStore, @@ -56,12 +56,14 @@ constructor( lifecycleScope.launch { it.activate() } } } + val configurationState: ConfigurationState = + configurationStore.forDisplay(displayId) ?: defaultConfigurationState lifecycleScope.launch { NotificationIconContainerViewBinder.bind( displayId = displayId, view = view, viewModel = viewModel, - configuration = configuration, + configuration = configurationState, systemBarUtilsState = systemBarUtilsState, failureTracker = failureTracker, viewStore = viewStore, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt index cdd02865bbee..cfd50973924d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt @@ -258,7 +258,7 @@ constructor( if (SceneContainerFlag.isEnabled) { listener?.let { listener -> launch { - viewModel.isHomeStatusBarAllowedByScene.collect { + viewModel.isHomeStatusBarAllowed.collect { listener.onIsHomeStatusBarAllowedBySceneChanged(it) } } @@ -503,7 +503,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) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index 9975aff938d6..3bae91a0ebe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -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 @@ -73,6 +76,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 @@ -142,13 +146,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> @@ -222,6 +225,7 @@ constructor( statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore, @Background bgScope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, + shadeDisplaysInteractor: Provider<ShadeDisplaysInteractor>, ) : HomeStatusBarViewModel, ExclusiveActivatable() { private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator") @@ -258,12 +262,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 @@ -271,9 +307,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( @@ -281,7 +315,6 @@ constructor( columnName = COL_ALLOWED_BY_SCENE, initialValue = false, ) - .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false) override val areNotificationsLightsOut: Flow<Boolean> = if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) { @@ -330,21 +363,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, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt index dbd1c9ce56fe..848fed6b0590 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt @@ -48,3 +48,15 @@ val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayReposit val Kosmos.sysuiStateInteractor by Fixture { SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository, displayRepository) } + +val Kosmos.sysUiStateOverrideFactory by Fixture { + { displayId: Int -> + SysUIStateOverride( + displayId, + sceneContainerPlugin, + dumpManager, + sysUiState, + sysUIStateDispatcher, + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt index a97c651ba426..5c4deaadffd5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.log.table.tableLogBufferFactory import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeDisplaysInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel @@ -40,29 +41,34 @@ import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStat import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarInteractor var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by + Kosmos.Fixture { homeStatusBarViewModelFactory.invoke(testableContext.displayId) } +var Kosmos.homeStatusBarViewModelFactory: (Int) -> HomeStatusBarViewModel by Kosmos.Fixture { - HomeStatusBarViewModelImpl( - testableContext.displayId, - batteryViewModelFactory, - tableLogBufferFactory, - homeStatusBarInteractor, - homeStatusBarIconBlockListInteractor, - lightsOutInteractor, - activeNotificationsInteractor, - darkIconInteractor, - headsUpNotificationInteractor, - keyguardTransitionInteractor, - keyguardInteractor, - statusBarOperatorNameViewModel, - sceneInteractor, - sceneContainerOcclusionInteractor, - shadeInteractor, - shareToAppChipViewModel, - ongoingActivityChipsViewModel, - statusBarPopupChipsViewModelFactory, - systemStatusEventAnimationInteractor, - multiDisplayStatusBarContentInsetsViewModelStore, - backgroundScope, - testDispatcher, - ) + { displayId -> + HomeStatusBarViewModelImpl( + displayId, + batteryViewModelFactory, + tableLogBufferFactory, + homeStatusBarInteractor, + homeStatusBarIconBlockListInteractor, + lightsOutInteractor, + activeNotificationsInteractor, + darkIconInteractor, + headsUpNotificationInteractor, + keyguardTransitionInteractor, + keyguardInteractor, + statusBarOperatorNameViewModel, + sceneInteractor, + sceneContainerOcclusionInteractor, + shadeInteractor, + shareToAppChipViewModel, + ongoingActivityChipsViewModel, + statusBarPopupChipsViewModelFactory, + systemStatusEventAnimationInteractor, + multiDisplayStatusBarContentInsetsViewModelStore, + backgroundScope, + testDispatcher, + { shadeDisplaysInteractor }, + ) + } } |