summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt105
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt73
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt54
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 },
+ )
+ }
}