diff options
| author | 2025-03-04 02:32:57 -0800 | |
|---|---|---|
| committer | 2025-03-04 02:32:57 -0800 | |
| commit | 1bc44b45acbc50ec25cd6435a5970ce3e718cb45 (patch) | |
| tree | ca7ee1668d05575dad1d45999579d508344fbff5 | |
| parent | 9ae2affb56991ae37efa9e94eb118a3947ba9631 (diff) | |
| parent | e5db5c65a4b40ef3f23666f6100dff93744af440 (diff) | |
Merge changes Ie34a4ac6,I78323f20,If95ab474,I3927a1fd into main
* changes:
Fix status bar icon size when shade goes around
Fix statusbar visibility wrt shade expansion in multiple displays
Propagate new shade displayId only after display change succeeded
Make SysUiState for external display an override of the default one
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 }, + ) + } } |