Loading packages/SystemUI/compose/core/src/com/android/compose/gesture/GesturesDisabled.kt 0 → 100644 +36 −0 Original line number Original line Diff line number Diff line /* * 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.compose.gesture import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput /** Disable gestures to this node (and descendants). */ fun Modifier.gesturesDisabled(): Modifier { return pointerInput(Unit) { awaitPointerEventScope { // we should wait for all new pointer events while (true) { awaitPointerEvent(pass = PointerEventPass.Initial) .changes .forEach(PointerInputChange::consume) } } } } packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +5 −1 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.transitions import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf Loading Loading @@ -162,7 +163,10 @@ constructor( viewModel = viewModel, viewModel = viewModel, headerViewModel = viewModel.qsContainerViewModel.shadeHeaderViewModel, headerViewModel = viewModel.qsContainerViewModel.shadeHeaderViewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, modifier = modifier.graphicsLayer { alpha = contentAlpha }, modifier = modifier .graphicsLayer { alpha = contentAlpha } .thenIf(brightnessMirrorShowing) { Modifier.gesturesDisabled() }, shadeSession = shadeSession, shadeSession = shadeSession, jankMonitor = jankMonitor, jankMonitor = jankMonitor, ) ) Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +8 −2 Original line number Original line Diff line number Diff line Loading @@ -60,8 +60,9 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.animation.scene.mechanics.TileRevealFlag import com.android.compose.animation.scene.mechanics.TileRevealFlag import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer Loading Loading @@ -164,7 +165,12 @@ constructor( LaunchedEffectWithLifecycle(key1 = Unit) { contentViewModel.detectShadeModeChanges() } LaunchedEffectWithLifecycle(key1 = Unit) { contentViewModel.detectShadeModeChanges() } Box(modifier = modifier.graphicsLayer { alpha = contentAlphaFromBrightnessMirror }) { Box( modifier = modifier .graphicsLayer { alpha = contentAlphaFromBrightnessMirror } .thenIf(showBrightnessMirror) { Modifier.gesturesDisabled() } ) { OverlayShade( OverlayShade( panelElement = QuickSettingsShade.Elements.Panel, panelElement = QuickSettingsShade.Elements.Panel, alignmentOnWideScreens = Alignment.TopEnd, alignmentOnWideScreens = Alignment.TopEnd, Loading packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +9 −1 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.transitions import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.padding import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf Loading Loading @@ -103,6 +104,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import dagger.Lazy import dagger.Lazy import javax.inject.Inject import javax.inject.Inject import kotlin.math.roundToInt import kotlin.math.roundToInt import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow object Shade { object Shade { Loading Loading @@ -431,7 +433,13 @@ private fun ContentScope.SplitShade( } } } } Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) { Box( modifier = modifier .fillMaxSize() .graphicsLayer { alpha = contentAlpha } .thenIf(brightnessMirrorShowing) { Modifier.gesturesDisabled() } ) { ShadePanelScrim(viewModel.isTransparencyEnabled) ShadePanelScrim(viewModel.isTransparencyEnabled) Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelTest.kt +16 −0 Original line number Original line Diff line number Diff line Loading @@ -21,11 +21,14 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.lifecycle.activateIn import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds Loading Loading @@ -136,4 +139,17 @@ class NotificationScrollViewModelTest : SysuiTestCase() { disposable.dispose() disposable.dispose() } } @Test fun noDualShade_brightnessMirrorShowing_notInteractive() = kosmos.runTest { disableDualShade() val interactive by collectLastValue(underTest.interactive) brightnessMirrorShowingInteractor.setMirrorShowing(false) assertThat(interactive).isTrue() brightnessMirrorShowingInteractor.setMirrorShowing(true) assertThat(interactive).isFalse() } } } Loading
packages/SystemUI/compose/core/src/com/android/compose/gesture/GesturesDisabled.kt 0 → 100644 +36 −0 Original line number Original line Diff line number Diff line /* * 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.compose.gesture import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput /** Disable gestures to this node (and descendants). */ fun Modifier.gesturesDisabled(): Modifier { return pointerInput(Unit) { awaitPointerEventScope { // we should wait for all new pointer events while (true) { awaitPointerEvent(pass = PointerEventPass.Initial) .changes .forEach(PointerInputChange::consume) } } } }
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +5 −1 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.transitions import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf Loading Loading @@ -162,7 +163,10 @@ constructor( viewModel = viewModel, viewModel = viewModel, headerViewModel = viewModel.qsContainerViewModel.shadeHeaderViewModel, headerViewModel = viewModel.qsContainerViewModel.shadeHeaderViewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, modifier = modifier.graphicsLayer { alpha = contentAlpha }, modifier = modifier .graphicsLayer { alpha = contentAlpha } .thenIf(brightnessMirrorShowing) { Modifier.gesturesDisabled() }, shadeSession = shadeSession, shadeSession = shadeSession, jankMonitor = jankMonitor, jankMonitor = jankMonitor, ) ) Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +8 −2 Original line number Original line Diff line number Diff line Loading @@ -60,8 +60,9 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.animation.scene.mechanics.TileRevealFlag import com.android.compose.animation.scene.mechanics.TileRevealFlag import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer Loading Loading @@ -164,7 +165,12 @@ constructor( LaunchedEffectWithLifecycle(key1 = Unit) { contentViewModel.detectShadeModeChanges() } LaunchedEffectWithLifecycle(key1 = Unit) { contentViewModel.detectShadeModeChanges() } Box(modifier = modifier.graphicsLayer { alpha = contentAlphaFromBrightnessMirror }) { Box( modifier = modifier .graphicsLayer { alpha = contentAlphaFromBrightnessMirror } .thenIf(showBrightnessMirror) { Modifier.gesturesDisabled() } ) { OverlayShade( OverlayShade( panelElement = QuickSettingsShade.Elements.Panel, panelElement = QuickSettingsShade.Elements.Panel, alignmentOnWideScreens = Alignment.TopEnd, alignmentOnWideScreens = Alignment.TopEnd, Loading
packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +9 −1 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.transitions import com.android.compose.gesture.gesturesDisabled import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import com.android.compose.modifiers.padding import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.modifiers.thenIf Loading Loading @@ -103,6 +104,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import dagger.Lazy import dagger.Lazy import javax.inject.Inject import javax.inject.Inject import kotlin.math.roundToInt import kotlin.math.roundToInt import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow object Shade { object Shade { Loading Loading @@ -431,7 +433,13 @@ private fun ContentScope.SplitShade( } } } } Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) { Box( modifier = modifier .fillMaxSize() .graphicsLayer { alpha = contentAlpha } .thenIf(brightnessMirrorShowing) { Modifier.gesturesDisabled() } ) { ShadePanelScrim(viewModel.isTransparencyEnabled) ShadePanelScrim(viewModel.isTransparencyEnabled) Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelTest.kt +16 −0 Original line number Original line Diff line number Diff line Loading @@ -21,11 +21,14 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.lifecycle.activateIn import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds Loading Loading @@ -136,4 +139,17 @@ class NotificationScrollViewModelTest : SysuiTestCase() { disposable.dispose() disposable.dispose() } } @Test fun noDualShade_brightnessMirrorShowing_notInteractive() = kosmos.runTest { disableDualShade() val interactive by collectLastValue(underTest.interactive) brightnessMirrorShowingInteractor.setMirrorShowing(false) assertThat(interactive).isTrue() brightnessMirrorShowingInteractor.setMirrorShowing(true) assertThat(interactive).isFalse() } } }