summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt145
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
14 files changed, 440 insertions, 63 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 2d58c8cad2b1..a266e7eb44a1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -49,7 +49,6 @@ import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import javax.inject.Provider
-import kotlinx.coroutines.flow.collectLatest
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -117,7 +116,7 @@ fun SceneContainer(
) {
"invalid ContentKey: $actionableContentKey"
}
- actionableContent.userActions.collectLatest { userActions ->
+ viewModel.filteredUserActions(actionableContent.userActions).collect { userActions ->
userActionsByContentKey[actionableContentKey] =
viewModel.resolveSceneFamilies(userActions)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 05a0119d68e4..bfcde7dab6d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -130,10 +130,6 @@ fun SceneScope.CollapsedShadeHeader(
modifier: Modifier = Modifier,
) {
val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
- val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
- if (isDisabled) {
- return
- }
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutHeight = LocalDisplayCutout.current.height()
@@ -196,7 +192,7 @@ fun SceneScope.CollapsedShadeHeader(
horizontalArrangement = Arrangement.End,
modifier =
Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
- .padding(horizontal = horizontalPadding)
+ .padding(horizontal = horizontalPadding),
) {
if (isLargeScreenLayout) {
ShadeCarrierGroup(
@@ -207,7 +203,7 @@ fun SceneScope.CollapsedShadeHeader(
SystemIconContainer(
viewModel = viewModel,
isClickable = isLargeScreenLayout,
- modifier = Modifier.align(Alignment.CenterVertically)
+ modifier = Modifier.align(Alignment.CenterVertically),
) {
StatusIcons(
viewModel = viewModel,
@@ -217,7 +213,7 @@ fun SceneScope.CollapsedShadeHeader(
modifier =
Modifier.align(Alignment.CenterVertically)
.padding(end = 6.dp)
- .weight(1f, fill = false)
+ .weight(1f, fill = false),
)
BatteryIcon(
createBatteryMeterViewController =
@@ -252,27 +248,15 @@ fun SceneScope.CollapsedShadeHeader(
CutoutLocation.NONE,
CutoutLocation.RIGHT -> {
startPlaceable.placeRelative(x = 0, y = 0)
- endPlaceable.placeRelative(
- x = startPlaceable.width,
- y = 0,
- )
+ endPlaceable.placeRelative(x = startPlaceable.width, y = 0)
}
CutoutLocation.CENTER -> {
startPlaceable.placeRelative(x = 0, y = 0)
- endPlaceable.placeRelative(
- x = startPlaceable.width + cutoutWidthPx,
- y = 0,
- )
+ endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0)
}
CutoutLocation.LEFT -> {
- startPlaceable.placeRelative(
- x = cutoutWidthPx,
- y = 0,
- )
- endPlaceable.placeRelative(
- x = startPlaceable.width + cutoutWidthPx,
- y = 0,
- )
+ startPlaceable.placeRelative(x = cutoutWidthPx, y = 0)
+ endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0)
}
}
}
@@ -288,10 +272,6 @@ fun SceneScope.ExpandedShadeHeader(
modifier: Modifier = Modifier,
) {
val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
- val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
- if (isDisabled) {
- return
- }
val useExpandedFormat by remember {
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
@@ -302,17 +282,14 @@ fun SceneScope.ExpandedShadeHeader(
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
if (isPrivacyChipVisible) {
Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
- PrivacyChip(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.CenterEnd),
- )
+ PrivacyChip(viewModel = viewModel, modifier = Modifier.align(Alignment.CenterEnd))
}
}
Column(
verticalArrangement = Arrangement.Bottom,
modifier =
Modifier.fillMaxWidth()
- .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
+ .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
Box {
@@ -362,11 +339,7 @@ fun SceneScope.ExpandedShadeHeader(
}
@Composable
-private fun SceneScope.Clock(
- scale: Float,
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier,
-) {
+private fun SceneScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) {
val layoutDirection = LocalLayoutDirection.current
Element(key = ShadeHeader.Elements.Clock, modifier = modifier) {
@@ -391,10 +364,10 @@ private fun SceneScope.Clock(
LayoutDirection.Ltr -> 0f
LayoutDirection.Rtl -> 1f
},
- 0.5f
+ 0.5f,
)
}
- .clickable { viewModel.onClockClicked() }
+ .clickable { viewModel.onClockClicked() },
)
}
}
@@ -447,10 +420,7 @@ private fun BatteryIcon(
}
@Composable
-private fun ShadeCarrierGroup(
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
Row(modifier = modifier) {
val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
@@ -465,11 +435,11 @@ private fun ShadeCarrierGroup(
viewModel =
(viewModel.mobileIconsViewModel.viewModelForSub(
subId,
- StatusBarLocation.SHADE_CARRIER_GROUP
+ StatusBarLocation.SHADE_CARRIER_GROUP,
) as ShadeCarrierGroupMobileIconViewModel),
)
.also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
- },
+ }
)
}
}
@@ -506,7 +476,7 @@ private fun SceneScope.StatusIcons(
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary),
Utils.getColorAttrDefaultColor(
themedContext,
- android.R.attr.textColorPrimaryInverse
+ android.R.attr.textColorPrimaryInverse,
),
)
statusBarIconController.addIconGroup(iconManager)
@@ -551,7 +521,7 @@ private fun SystemIconContainer(
viewModel: ShadeHeaderViewModel,
isClickable: Boolean,
modifier: Modifier = Modifier,
- content: @Composable RowScope.() -> Unit
+ content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isHovered by interactionSource.collectIsHoveredAsState()
@@ -578,10 +548,7 @@ private fun SystemIconContainer(
}
@Composable
-private fun SceneScope.PrivacyChip(
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun SceneScope.PrivacyChip(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle()
AndroidView(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt
new file mode 100644
index 000000000000..08225a7770d2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 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.scene.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DisabledContentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val underTest = kosmos.disabledContentInteractor
+
+ @Test
+ fun isDisabled_notificationsShade() =
+ kosmos.runTest {
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NONE)
+ assertThat(underTest.isDisabled(Overlays.NotificationsShade)).isFalse()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(underTest.isDisabled(Overlays.NotificationsShade)).isTrue()
+ }
+
+ @Test
+ fun isDisabled_qsShade() =
+ kosmos.runTest {
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NONE)
+ assertThat(underTest.isDisabled(Overlays.QuickSettingsShade)).isFalse()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(underTest.isDisabled(Overlays.QuickSettingsShade)).isTrue()
+ }
+
+ @Test
+ fun repeatWhenDisabled() =
+ kosmos.runTest {
+ var notificationDisabledCount = 0
+ applicationCoroutineScope.launch {
+ underTest.repeatWhenDisabled(Overlays.NotificationsShade) {
+ notificationDisabledCount++
+ }
+ }
+ var qsDisabledCount = 0
+ applicationCoroutineScope.launch {
+ underTest.repeatWhenDisabled(Overlays.QuickSettingsShade) { qsDisabledCount++ }
+ }
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(notificationDisabledCount).isEqualTo(0)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 =
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE or
+ StatusBarManager.DISABLE2_QUICK_SETTINGS
+ )
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(2)
+ }
+
+ @Test
+ fun filteredUserActions() =
+ kosmos.runTest {
+ val map =
+ mapOf<UserAction, UserActionResult>(
+ Swipe.Up to UserActionResult.ShowOverlay(Overlays.NotificationsShade),
+ Swipe.Down to UserActionResult.ShowOverlay(Overlays.QuickSettingsShade),
+ )
+ val unfiltered = MutableStateFlow(map)
+ val filtered by collectLastValue(underTest.filteredUserActions(unfiltered))
+ assertThat(filtered).isEqualTo(map)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(filtered)
+ .isEqualTo(
+ mapOf(Swipe.Down to UserActionResult.ShowOverlay(Overlays.QuickSettingsShade))
+ )
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(filtered)
+ .isEqualTo(
+ mapOf(Swipe.Up to UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+ )
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 =
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE or
+ StatusBarManager.DISABLE2_QUICK_SETTINGS
+ )
+ assertThat(filtered).isEqualTo(emptyMap<UserAction, UserActionResult>())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 7fe3d8d08afa..48edded5df18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.interactor
+import android.app.StatusBarManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -30,6 +31,8 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
@@ -43,6 +46,8 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -523,4 +528,51 @@ class SceneInteractorTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(Scenes.Gone)
}
+
+ @Test
+ fun showOverlay_overlayDisabled_doesNothing() =
+ kosmos.runTest {
+ val currentOverlays by collectLastValue(underTest.currentOverlays)
+ val disabledOverlay = Overlays.QuickSettingsShade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(disabledContentInteractor.isDisabled(disabledOverlay)).isTrue()
+ assertThat(currentOverlays).doesNotContain(disabledOverlay)
+
+ underTest.showOverlay(disabledOverlay, "reason")
+
+ assertThat(currentOverlays).doesNotContain(disabledOverlay)
+ }
+
+ @Test
+ fun replaceOverlay_withDisabledOverlay_doesNothing() =
+ kosmos.runTest {
+ val currentOverlays by collectLastValue(underTest.currentOverlays)
+ val showingOverlay = Overlays.NotificationsShade
+ underTest.showOverlay(showingOverlay, "reason")
+ assertThat(currentOverlays).isEqualTo(setOf(showingOverlay))
+ val disabledOverlay = Overlays.QuickSettingsShade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(disabledContentInteractor.isDisabled(disabledOverlay)).isTrue()
+
+ underTest.replaceOverlay(showingOverlay, disabledOverlay, "reason")
+
+ assertThat(currentOverlays).isEqualTo(setOf(showingOverlay))
+ }
+
+ @Test
+ fun changeScene_toDisabledScene_doesNothing() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ val disabledScene = Scenes.Shade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(disabledContentInteractor.isDisabled(disabledScene)).isTrue()
+ assertThat(currentScene).isNotEqualTo(disabledScene)
+
+ underTest.changeScene(disabledScene, "reason")
+
+ assertThat(currentScene).isNotEqualTo(disabledScene)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index cca847effe94..5d49c113a539 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -81,6 +81,9 @@ import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscree
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -101,6 +104,8 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
@@ -2673,6 +2678,25 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(isAlternateBouncerVisible).isFalse()
}
+ @Test
+ fun handleDisableFlags() =
+ kosmos.runTest {
+ underTest.start()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.Shade, "reason")
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ runCurrent()
+
+ assertThat(currentScene).isNotEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt
new file mode 100644
index 000000000000..d7c3b6b43c71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.scene.domain.interactor
+
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+class DisabledContentInteractor
+@Inject
+constructor(private val disableFlagsInteractor: DisableFlagsInteractor) {
+
+ /** Returns `true` if the given [key] is disabled; `false` if it's enabled */
+ fun isDisabled(
+ key: ContentKey,
+ disabledFlags: DisableFlagsModel = disableFlagsInteractor.disableFlags.value,
+ ): Boolean {
+ return with(disabledFlags) {
+ when (key) {
+ Scenes.Shade,
+ Overlays.NotificationsShade -> !isShadeEnabled()
+ Scenes.QuickSettings,
+ Overlays.QuickSettingsShade -> !isQuickSettingsEnabled()
+ else -> false
+ }
+ }
+ }
+
+ /** Runs the given [block] each time that [key] becomes disabled. */
+ suspend fun repeatWhenDisabled(key: ContentKey, block: suspend (disabled: ContentKey) -> Unit) {
+ disableFlagsInteractor.disableFlags
+ .map { isDisabled(key) }
+ .distinctUntilChanged()
+ .collectLatest { isDisabled ->
+ if (isDisabled) {
+ block(key)
+ }
+ }
+ }
+
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return combine(disableFlagsInteractor.disableFlags, unfiltered) {
+ disabledFlags,
+ unfilteredMap ->
+ unfilteredMap.filterValues { actionResult ->
+ val destination =
+ when (actionResult) {
+ is UserActionResult.ChangeScene -> actionResult.toScene
+ is UserActionResult.ShowOverlay -> actionResult.overlay
+ is UserActionResult.ReplaceByOverlay -> actionResult.overlay
+ else -> null
+ }
+ if (destination != null) {
+ // results that lead to a disabled destination get filtered out.
+ !isDisabled(key = destination, disabledFlags = disabledFlags)
+ } else {
+ // Action results that don't lead to a destination are never filtered out.
+ true
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index f20e5a54f6ed..d83d74e4e538 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -21,6 +21,8 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
@@ -64,6 +66,7 @@ constructor(
private val sceneFamilyResolvers: Lazy<Map<SceneKey, @JvmSuppressWildcards SceneResolver>>,
private val deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
private val keyguardEnabledInteractor: Lazy<KeyguardEnabledInteractor>,
+ private val disabledContentInteractor: DisabledContentInteractor,
) {
interface OnSceneAboutToChangeListener {
@@ -465,6 +468,10 @@ constructor(
return false
}
+ if (disabledContentInteractor.isDisabled(to)) {
+ return false
+ }
+
val inMidTransitionFromGone =
(transitionState.value as? ObservableTransitionState.Transition)?.fromContent ==
Scenes.Gone
@@ -503,6 +510,10 @@ constructor(
" Logging reason for overlay change was: $loggingReason"
}
+ if (to != null && disabledContentInteractor.isDisabled(to)) {
+ return false
+ }
+
val isFromValid = (from == null) || (from in currentOverlays.value)
val isToValid =
(to == null) || (to !in currentOverlays.value && to in repository.allContentKeys)
@@ -517,4 +528,14 @@ constructor(
/** Returns `true` if [scene] can be resolved from [family]. */
fun isSceneInFamily(scene: SceneKey, family: SceneKey): Boolean =
sceneFamilyResolvers.get()[family]?.includesScene(scene) == true
+
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return disabledContentInteractor.filteredUserActions(unfiltered)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index aece5c65ce12..8d8c24eae9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,7 +19,6 @@
package com.android.systemui.scene.domain.startable
import android.app.StatusBarManager
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
@@ -56,6 +55,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.interactor.DisabledContentInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -103,6 +103,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -144,6 +145,7 @@ constructor(
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val vibratorHelper: VibratorHelper,
private val msdlPlayer: MSDLPlayer,
+ private val disabledContentInteractor: DisabledContentInteractor,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -281,6 +283,7 @@ constructor(
handlePowerState()
handleDreamState()
handleShadeTouchability()
+ handleDisableFlags()
}
private fun handleBouncerImeVisibility() {
@@ -565,6 +568,38 @@ constructor(
}
}
+ private fun handleDisableFlags() {
+ applicationScope.launch {
+ launch {
+ sceneInteractor.currentScene.collectLatest { currentScene ->
+ disabledContentInteractor.repeatWhenDisabled(currentScene) {
+ switchToScene(
+ targetSceneKey = SceneFamilies.Home,
+ loggingReason =
+ "Current scene ${currentScene.debugName} became" + " disabled",
+ )
+ }
+ }
+ }
+
+ launch {
+ sceneInteractor.currentOverlays.collectLatest { overlays ->
+ overlays.forEach { overlay ->
+ launch {
+ disabledContentInteractor.repeatWhenDisabled(overlay) {
+ sceneInteractor.hideOverlay(
+ overlay = overlay,
+ loggingReason =
+ "Overlay ${overlay.debugName} became" + " disabled",
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
private fun handleDeviceEntryHapticsWhileDeviceLocked() {
applicationScope.launch {
deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 32d5cb460cd8..c1e8032aa9e5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -280,6 +280,16 @@ constructor(
}
}
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return sceneInteractor.filteredUserActions(unfiltered)
+ }
+
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 45516aa69cd7..0d847d84c820 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -23,6 +23,7 @@ import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.os.UserHandle
import android.provider.Settings
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.ActivityStarter
@@ -48,7 +49,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Models UI state for the shade header. */
class ShadeHeaderViewModel
@@ -87,10 +87,6 @@ constructor(
/** Whether or not the privacy chip is enabled in the device privacy config. */
val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
- private val _isDisabled = MutableStateFlow(false)
- /** Whether or not the Shade Header should be disabled based on disableFlags. */
- val isDisabled: StateFlow<Boolean> = _isDisabled.asStateFlow()
-
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
@@ -132,8 +128,6 @@ constructor(
.collect { _mobileSubIds.value = it }
}
- launch { shadeInteractor.isQsEnabled.map { !it }.collect { _isDisabled.value = it } }
-
awaitCancellation()
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 72cb1dfe38db..1556058d51ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,12 @@
package com.android.systemui.kosmos
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
+import com.android.systemui.util.mockito.mock
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -32,12 +36,15 @@ fun Kosmos.useStandardTestDispatcher() = apply { testDispatcher = StandardTestDi
fun Kosmos.useUnconfinedTestDispatcher() = apply { testDispatcher = UnconfinedTestDispatcher() }
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
-var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
+var Kosmos.backgroundScope by Fixture { testScope.backgroundScope }
+var Kosmos.applicationCoroutineScope by Fixture { backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
- testScope.backgroundScope.coroutineContext
+ backgroundScope.coroutineContext
}
var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.brightnessWarningToast: BrightnessWarningToast by
+ Kosmos.Fixture { mock<BrightnessWarningToast>() }
/**
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
@@ -49,3 +56,5 @@ fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) =
fun Kosmos.runCurrent() = testScope.runCurrent()
fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
+
+fun <T> Kosmos.collectValues(flow: Flow<T>): FlowValue<List<T>> = testScope.collectValues(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt
new file mode 100644
index 000000000000..12d4e90dc469
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
+
+val Kosmos.disabledContentInteractor by Fixture {
+ DisabledContentInteractor(disableFlagsInteractor = disableFlagsInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index f84c3bdfdaf1..eb352baab0e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -33,5 +33,6 @@ val Kosmos.sceneInteractor: SceneInteractor by
sceneFamilyResolvers = { sceneFamilyResolvers },
deviceUnlockedInteractor = { deviceUnlockedInteractor },
keyguardEnabledInteractor = { keyguardEnabledInteractor },
+ disabledContentInteractor = disabledContentInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 7e6a7271c561..82b5f6332b23 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -37,6 +37,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.disabledContentInteractor
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -83,5 +84,6 @@ val Kosmos.sceneContainerStartable by Fixture {
alternateBouncerInteractor = alternateBouncerInteractor,
vibratorHelper = vibratorHelper,
msdlPlayer = msdlPlayer,
+ disabledContentInteractor = disabledContentInteractor,
)
}