summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt196
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorTest.kt274
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetector.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorKosmos.kt29
20 files changed, 362 insertions, 498 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 619b4280d954..aa0d474ba41c 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
@@ -204,7 +204,7 @@ fun SceneContainer(
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
- swipeSourceDetector = viewModel.edgeDetector,
+ swipeSourceDetector = viewModel.swipeSourceDetector,
) {
sceneByKey.forEach { (sceneKey, scene) ->
scene(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index b66e2fe13e8a..47ca4b14a26f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -41,7 +41,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
@@ -275,20 +275,20 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
assertThat(downDestination?.transitionKey).isNull()
}
- val downFromTopRightDestination =
+ val downFromEndHalfDestination =
userActions?.get(
Swipe.Down(
- fromSource = SceneContainerEdge.TopRight,
+ fromSource = SceneContainerArea.EndHalf,
pointerCount = if (downWithTwoPointers) 2 else 1,
)
)
when {
- !isShadeTouchable -> assertThat(downFromTopRightDestination).isNull()
- downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
+ !isShadeTouchable -> assertThat(downFromEndHalfDestination).isNull()
+ downWithTwoPointers -> assertThat(downFromEndHalfDestination).isNull()
else -> {
- assertThat(downFromTopRightDestination)
+ assertThat(downFromEndHalfDestination)
.isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
- assertThat(downFromTopRightDestination?.transitionKey).isNull()
+ assertThat(downFromEndHalfDestination?.transitionKey).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
index 52b9e47e6d3d..52a0a5445002 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -30,7 +30,7 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayActionsViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -71,13 +71,13 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
}
@Test
- fun downFromTopRight_switchesToQuickSettingsShade() =
+ fun downFromTopEnd_switchesToQuickSettingsShade() =
testScope.runTest {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
val action =
- (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight)) as? ShowOverlay)
+ (actions?.get(Swipe.Down(fromSource = SceneContainerArea.EndHalf)) as? ShowOverlay)
assertThat(action?.overlay).isEqualTo(Overlays.QuickSettingsShade)
val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
assertThat(overlaysToHide).isNotNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index df2dd99c779e..b98059a1fe90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -31,7 +31,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -84,13 +84,14 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
}
@Test
- fun downFromTopLeft_switchesToNotificationsShade() =
+ fun downFromTopStart_switchesToNotificationsShade() =
testScope.runTest {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
val action =
- (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft)) as? ShowOverlay)
+ (actions?.get(Swipe.Down(fromSource = SceneContainerArea.StartHalf))
+ as? ShowOverlay)
assertThat(action?.overlay).isEqualTo(Overlays.NotificationsShade)
val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
assertThat(overlaysToHide).isNotNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt
new file mode 100644
index 000000000000..a09e5cd9de9b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.EndEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.EndHalf
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.BottomEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.LeftEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.LeftHalf
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.RightEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.RightHalf
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.StartEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.StartHalf
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneContainerSwipeDetectorTest : SysuiTestCase() {
+
+ private val edgeSize = 40
+ private val screenWidth = 800
+ private val screenHeight = 600
+
+ private val underTest = SceneContainerSwipeDetector(edgeSize = edgeSize.dp)
+
+ @Test
+ fun source_noEdge_detectsLeftHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = screenWidth / 2 - 1, y = screenHeight / 2)
+ assertThat(detectedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun source_swipeVerticallyOnTopLeft_detectsLeftHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun source_swipeHorizontallyOnTopLeft_detectsLeftEdge() {
+ val detectedEdge = swipeHorizontallyFrom(x = 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(LeftEdge)
+ }
+
+ @Test
+ fun source_swipeVerticallyOnTopRight_detectsRightHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = screenWidth - 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(RightHalf)
+ }
+
+ @Test
+ fun source_swipeHorizontallyOnTopRight_detectsRightEdge() {
+ val detectedEdge = swipeHorizontallyFrom(x = screenWidth - 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(RightEdge)
+ }
+
+ @Test
+ fun source_swipeVerticallyToLeftOfSplit_detectsLeftHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) - 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun source_swipeVerticallyToRightOfSplit_detectsRightHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) + 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(RightHalf)
+ }
+
+ @Test
+ fun source_swipeVerticallyOnBottom_detectsBottomEdge() {
+ val detectedEdge =
+ swipeVerticallyFrom(x = screenWidth / 3, y = screenHeight - (edgeSize / 2))
+ assertThat(detectedEdge).isEqualTo(BottomEdge)
+ }
+
+ @Test
+ fun source_swipeHorizontallyOnBottom_detectsLeftHalf() {
+ val detectedEdge =
+ swipeHorizontallyFrom(x = screenWidth / 3, y = screenHeight - (edgeSize - 1))
+ assertThat(detectedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun source_swipeHorizontallyOnLeft_detectsLeftEdge() {
+ val detectedEdge = swipeHorizontallyFrom(x = edgeSize - 1, y = screenHeight / 2)
+ assertThat(detectedEdge).isEqualTo(LeftEdge)
+ }
+
+ @Test
+ fun source_swipeVerticallyOnLeft_detectsLeftHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = edgeSize - 1, y = screenHeight / 2)
+ assertThat(detectedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun source_swipeHorizontallyOnRight_detectsRightEdge() {
+ val detectedEdge =
+ swipeHorizontallyFrom(x = screenWidth - edgeSize + 1, y = screenHeight / 2)
+ assertThat(detectedEdge).isEqualTo(RightEdge)
+ }
+
+ @Test
+ fun source_swipeVerticallyOnRight_detectsRightHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = screenWidth - edgeSize + 1, y = screenHeight / 2)
+ assertThat(detectedEdge).isEqualTo(RightHalf)
+ }
+
+ @Test
+ fun resolve_startEdgeInLtr_resolvesLeftEdge() {
+ val resolvedEdge = StartEdge.resolve(LayoutDirection.Ltr)
+ assertThat(resolvedEdge).isEqualTo(LeftEdge)
+ }
+
+ @Test
+ fun resolve_startEdgeInRtl_resolvesRightEdge() {
+ val resolvedEdge = StartEdge.resolve(LayoutDirection.Rtl)
+ assertThat(resolvedEdge).isEqualTo(RightEdge)
+ }
+
+ @Test
+ fun resolve_endEdgeInLtr_resolvesRightEdge() {
+ val resolvedEdge = EndEdge.resolve(LayoutDirection.Ltr)
+ assertThat(resolvedEdge).isEqualTo(RightEdge)
+ }
+
+ @Test
+ fun resolve_endEdgeInRtl_resolvesLeftEdge() {
+ val resolvedEdge = EndEdge.resolve(LayoutDirection.Rtl)
+ assertThat(resolvedEdge).isEqualTo(LeftEdge)
+ }
+
+ @Test
+ fun resolve_startHalfInLtr_resolvesLeftHalf() {
+ val resolvedEdge = StartHalf.resolve(LayoutDirection.Ltr)
+ assertThat(resolvedEdge).isEqualTo(LeftHalf)
+ }
+
+ @Test
+ fun resolve_startHalfInRtl_resolvesRightHalf() {
+ val resolvedEdge = StartHalf.resolve(LayoutDirection.Rtl)
+ assertThat(resolvedEdge).isEqualTo(RightHalf)
+ }
+
+ @Test
+ fun resolve_endHalfInLtr_resolvesRightHalf() {
+ val resolvedEdge = EndHalf.resolve(LayoutDirection.Ltr)
+ assertThat(resolvedEdge).isEqualTo(RightHalf)
+ }
+
+ @Test
+ fun resolve_endHalfInRtl_resolvesLeftHalf() {
+ val resolvedEdge = EndHalf.resolve(LayoutDirection.Rtl)
+ assertThat(resolvedEdge).isEqualTo(LeftHalf)
+ }
+
+ private fun swipeVerticallyFrom(x: Int, y: Int): SceneContainerArea.Resolved? {
+ return swipeFrom(x, y, Orientation.Vertical)
+ }
+
+ private fun swipeHorizontallyFrom(x: Int, y: Int): SceneContainerArea.Resolved? {
+ return swipeFrom(x, y, Orientation.Horizontal)
+ }
+
+ private fun swipeFrom(x: Int, y: Int, orientation: Orientation): SceneContainerArea.Resolved? {
+ return underTest.source(
+ layoutSize = IntSize(width = screenWidth, height = screenHeight),
+ position = IntOffset(x, y),
+ density = Density(1f),
+ orientation = orientation,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 30d9f73d7441..adaebbd27986 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -48,6 +48,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -55,6 +56,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
@@ -324,7 +326,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
kosmos.enableSingleShade()
assertThat(shadeMode).isEqualTo(ShadeMode.Single)
- assertThat(underTest.edgeDetector).isEqualTo(DefaultEdgeDetector)
+ assertThat(underTest.swipeSourceDetector).isEqualTo(DefaultEdgeDetector)
}
@Test
@@ -334,26 +336,28 @@ class SceneContainerViewModelTest : SysuiTestCase() {
kosmos.enableSplitShade()
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
- assertThat(underTest.edgeDetector).isEqualTo(DefaultEdgeDetector)
+ assertThat(underTest.swipeSourceDetector).isEqualTo(DefaultEdgeDetector)
}
@Test
- fun edgeDetector_dualShade_narrowScreen_usesSplitEdgeDetector() =
+ fun edgeDetector_dualShade_narrowScreen_usesSceneContainerSwipeDetector() =
testScope.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableDualShade(wideLayout = false)
assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
- assertThat(underTest.edgeDetector).isEqualTo(kosmos.splitEdgeDetector)
+ assertThat(underTest.swipeSourceDetector)
+ .isInstanceOf(SceneContainerSwipeDetector::class.java)
}
@Test
- fun edgeDetector_dualShade_wideScreen_usesSplitEdgeDetector() =
+ fun edgeDetector_dualShade_wideScreen_usesSceneContainerSwipeDetector() =
testScope.runTest {
val shadeMode by collectLastValue(kosmos.shadeMode)
kosmos.enableDualShade(wideLayout = true)
assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
- assertThat(underTest.edgeDetector).isEqualTo(kosmos.splitEdgeDetector)
+ assertThat(underTest.swipeSourceDetector)
+ .isInstanceOf(SceneContainerSwipeDetector::class.java)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorTest.kt
deleted file mode 100644
index 3d76d280b2cc..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorTest.kt
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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.ui.viewmodel
-
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.End
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Resolved.Bottom
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Resolved.Left
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Resolved.Right
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Resolved.TopLeft
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Resolved.TopRight
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Start
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopEnd
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopStart
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SplitEdgeDetectorTest : SysuiTestCase() {
-
- private val edgeSize = 40
- private val screenWidth = 800
- private val screenHeight = 600
-
- private var edgeSplitFraction = 0.7f
-
- private val underTest =
- SplitEdgeDetector(
- topEdgeSplitFraction = { edgeSplitFraction },
- edgeSize = edgeSize.dp,
- )
-
- @Test
- fun source_noEdge_detectsNothing() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = screenWidth / 2,
- y = screenHeight / 2,
- )
- assertThat(detectedEdge).isNull()
- }
-
- @Test
- fun source_swipeVerticallyOnTopLeft_detectsTopLeft() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(TopLeft)
- }
-
- @Test
- fun source_swipeHorizontallyOnTopLeft_detectsLeft() {
- val detectedEdge =
- swipeHorizontallyFrom(
- x = 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(Left)
- }
-
- @Test
- fun source_swipeVerticallyOnTopRight_detectsTopRight() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = screenWidth - 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(TopRight)
- }
-
- @Test
- fun source_swipeHorizontallyOnTopRight_detectsRight() {
- val detectedEdge =
- swipeHorizontallyFrom(
- x = screenWidth - 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(Right)
- }
-
- @Test
- fun source_swipeVerticallyToLeftOfSplit_detectsTopLeft() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = (screenWidth * edgeSplitFraction).toInt() - 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(TopLeft)
- }
-
- @Test
- fun source_swipeVerticallyToRightOfSplit_detectsTopRight() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = (screenWidth * edgeSplitFraction).toInt() + 1,
- y = edgeSize - 1,
- )
- assertThat(detectedEdge).isEqualTo(TopRight)
- }
-
- @Test
- fun source_edgeSplitFractionUpdatesDynamically() {
- val middleX = (screenWidth * 0.5f).toInt()
- val topY = 0
-
- // Split closer to the right; middle of screen is considered "left".
- edgeSplitFraction = 0.6f
- assertThat(swipeVerticallyFrom(x = middleX, y = topY)).isEqualTo(TopLeft)
-
- // Split closer to the left; middle of screen is considered "right".
- edgeSplitFraction = 0.4f
- assertThat(swipeVerticallyFrom(x = middleX, y = topY)).isEqualTo(TopRight)
-
- // Illegal fraction.
- edgeSplitFraction = 1.2f
- assertFailsWith<IllegalArgumentException> { swipeVerticallyFrom(x = middleX, y = topY) }
-
- // Illegal fraction.
- edgeSplitFraction = -0.3f
- assertFailsWith<IllegalArgumentException> { swipeVerticallyFrom(x = middleX, y = topY) }
- }
-
- @Test
- fun source_swipeVerticallyOnBottom_detectsBottom() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = screenWidth / 3,
- y = screenHeight - (edgeSize / 2),
- )
- assertThat(detectedEdge).isEqualTo(Bottom)
- }
-
- @Test
- fun source_swipeHorizontallyOnBottom_detectsNothing() {
- val detectedEdge =
- swipeHorizontallyFrom(
- x = screenWidth / 3,
- y = screenHeight - (edgeSize - 1),
- )
- assertThat(detectedEdge).isNull()
- }
-
- @Test
- fun source_swipeHorizontallyOnLeft_detectsLeft() {
- val detectedEdge =
- swipeHorizontallyFrom(
- x = edgeSize - 1,
- y = screenHeight / 2,
- )
- assertThat(detectedEdge).isEqualTo(Left)
- }
-
- @Test
- fun source_swipeVerticallyOnLeft_detectsNothing() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = edgeSize - 1,
- y = screenHeight / 2,
- )
- assertThat(detectedEdge).isNull()
- }
-
- @Test
- fun source_swipeHorizontallyOnRight_detectsRight() {
- val detectedEdge =
- swipeHorizontallyFrom(
- x = screenWidth - edgeSize + 1,
- y = screenHeight / 2,
- )
- assertThat(detectedEdge).isEqualTo(Right)
- }
-
- @Test
- fun source_swipeVerticallyOnRight_detectsNothing() {
- val detectedEdge =
- swipeVerticallyFrom(
- x = screenWidth - edgeSize + 1,
- y = screenHeight / 2,
- )
- assertThat(detectedEdge).isNull()
- }
-
- @Test
- fun resolve_startInLtr_resolvesLeft() {
- val resolvedEdge = Start.resolve(LayoutDirection.Ltr)
- assertThat(resolvedEdge).isEqualTo(Left)
- }
-
- @Test
- fun resolve_startInRtl_resolvesRight() {
- val resolvedEdge = Start.resolve(LayoutDirection.Rtl)
- assertThat(resolvedEdge).isEqualTo(Right)
- }
-
- @Test
- fun resolve_endInLtr_resolvesRight() {
- val resolvedEdge = End.resolve(LayoutDirection.Ltr)
- assertThat(resolvedEdge).isEqualTo(Right)
- }
-
- @Test
- fun resolve_endInRtl_resolvesLeft() {
- val resolvedEdge = End.resolve(LayoutDirection.Rtl)
- assertThat(resolvedEdge).isEqualTo(Left)
- }
-
- @Test
- fun resolve_topStartInLtr_resolvesTopLeft() {
- val resolvedEdge = TopStart.resolve(LayoutDirection.Ltr)
- assertThat(resolvedEdge).isEqualTo(TopLeft)
- }
-
- @Test
- fun resolve_topStartInRtl_resolvesTopRight() {
- val resolvedEdge = TopStart.resolve(LayoutDirection.Rtl)
- assertThat(resolvedEdge).isEqualTo(TopRight)
- }
-
- @Test
- fun resolve_topEndInLtr_resolvesTopRight() {
- val resolvedEdge = TopEnd.resolve(LayoutDirection.Ltr)
- assertThat(resolvedEdge).isEqualTo(TopRight)
- }
-
- @Test
- fun resolve_topEndInRtl_resolvesTopLeft() {
- val resolvedEdge = TopEnd.resolve(LayoutDirection.Rtl)
- assertThat(resolvedEdge).isEqualTo(TopLeft)
- }
-
- private fun swipeVerticallyFrom(x: Int, y: Int): SceneContainerEdge.Resolved? {
- return swipeFrom(x, y, Orientation.Vertical)
- }
-
- private fun swipeHorizontallyFrom(x: Int, y: Int): SceneContainerEdge.Resolved? {
- return swipeFrom(x, y, Orientation.Horizontal)
- }
-
- private fun swipeFrom(x: Int, y: Int, orientation: Orientation): SceneContainerEdge.Resolved? {
- return underTest.source(
- layoutSize = IntSize(width = screenWidth, height = screenHeight),
- position = IntOffset(x, y),
- density = Density(1f),
- orientation = orientation,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index 1b9251061f3d..9319961f5b68 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -24,7 +24,7 @@ import com.android.compose.animation.scene.UserActionResult.HideOverlay
import com.android.compose.animation.scene.UserActionResult.ShowOverlay
import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -38,7 +38,7 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() :
mapOf(
Swipe.Up to HideOverlay(Overlays.NotificationsShade),
Back to HideOverlay(Overlays.NotificationsShade),
- Swipe.Down(fromSource = SceneContainerEdge.TopRight) to
+ Swipe.Down(fromSource = SceneContainerArea.EndHalf) to
ShowOverlay(
Overlays.QuickSettingsShade,
hideCurrentOverlays = HideCurrentOverlays.Some(Overlays.NotificationsShade),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 5bc26f50f70f..52c4e2fac6d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -25,7 +25,7 @@ import com.android.compose.animation.scene.UserActionResult.ShowOverlay
import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -47,7 +47,7 @@ constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewM
put(Back, HideOverlay(Overlays.QuickSettingsShade))
}
put(
- Swipe.Down(fromSource = SceneContainerEdge.TopLeft),
+ Swipe.Down(fromSource = SceneContainerArea.StartHalf),
ShowOverlay(
Overlays.NotificationsShade,
hideCurrentOverlays =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index a4949ad66109..caa61617505f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import androidx.compose.ui.unit.dp
import com.android.systemui.CoreStartable
import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
import com.android.systemui.scene.domain.SceneDomainModule
@@ -30,8 +29,6 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
-import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -99,15 +96,5 @@ interface KeyguardlessSceneContainerFrameworkModule {
transitionsBuilder = SceneContainerTransitions(),
)
}
-
- @Provides
- fun splitEdgeDetector(shadeInteractor: ShadeInteractor): SplitEdgeDetector {
- return SplitEdgeDetector(
- topEdgeSplitFraction = shadeInteractor::getTopEdgeSplitFraction,
- // TODO(b/338577208): This should be 60dp at the top in the dual-shade UI. Better to
- // replace this constant with dynamic window insets.
- edgeSize = 40.dp,
- )
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index a018283c3953..ea11d202b119 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import androidx.compose.ui.unit.dp
import com.android.systemui.CoreStartable
import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
import com.android.systemui.scene.domain.SceneDomainModule
@@ -30,8 +29,6 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
-import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -121,15 +118,5 @@ interface SceneContainerFrameworkModule {
transitionsBuilder = SceneContainerTransitions(),
)
}
-
- @Provides
- fun splitEdgeDetector(shadeInteractor: ShadeInteractor): SplitEdgeDetector {
- return SplitEdgeDetector(
- topEdgeSplitFraction = shadeInteractor::getTopEdgeSplitFraction,
- // TODO(b/338577208): This should be 60dp at the top in the dual-shade UI. Better to
- // replace this constant with dynamic window insets.
- edgeSize = 40.dp,
- )
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt
new file mode 100644
index 000000000000..ede453dbe6b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.FixedSizeEdgeDetector
+import com.android.compose.animation.scene.SwipeSource
+import com.android.compose.animation.scene.SwipeSourceDetector
+
+/** Identifies an area of the [SceneContainer] to detect swipe gestures on. */
+sealed class SceneContainerArea(private val resolveArea: (LayoutDirection) -> Resolved) :
+ SwipeSource {
+ data object StartEdge :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.LeftEdge else Resolved.RightEdge
+ }
+ )
+
+ data object StartHalf :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.LeftHalf else Resolved.RightHalf
+ }
+ )
+
+ data object EndEdge :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.RightEdge else Resolved.LeftEdge
+ }
+ )
+
+ data object EndHalf :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.RightHalf else Resolved.LeftHalf
+ }
+ )
+
+ override fun resolve(layoutDirection: LayoutDirection): Resolved {
+ return resolveArea(layoutDirection)
+ }
+
+ sealed interface Resolved : SwipeSource.Resolved {
+ data object LeftEdge : Resolved
+
+ data object LeftHalf : Resolved
+
+ data object BottomEdge : Resolved
+
+ data object RightEdge : Resolved
+
+ data object RightHalf : Resolved
+ }
+}
+
+/**
+ * A [SwipeSourceDetector] that detects edges similarly to [FixedSizeEdgeDetector], but additionally
+ * detects the left and right halves of the screen (besides the edges).
+ *
+ * Corner cases (literally): A vertical swipe on the top-left corner of the screen will be resolved
+ * to [SceneContainerArea.Resolved.LeftHalf], whereas a horizontal swipe in the same position will
+ * be resolved to [SceneContainerArea.Resolved.LeftEdge]. The behavior is similar on the top-right
+ * corner of the screen.
+ *
+ * Callers who need to detect the start and end edges based on the layout direction (LTR vs RTL)
+ * should subscribe to [SceneContainerArea.StartEdge] and [SceneContainerArea.EndEdge] instead.
+ * These will be resolved at runtime to [SceneContainerArea.Resolved.LeftEdge] and
+ * [SceneContainerArea.Resolved.RightEdge] appropriately. Similarly, [SceneContainerArea.StartHalf]
+ * and [SceneContainerArea.EndHalf] will be resolved appropriately to
+ * [SceneContainerArea.Resolved.LeftHalf] and [SceneContainerArea.Resolved.RightHalf].
+ *
+ * @param edgeSize The fixed size of each edge.
+ */
+class SceneContainerSwipeDetector(val edgeSize: Dp) : SwipeSourceDetector {
+
+ private val fixedEdgeDetector = FixedSizeEdgeDetector(edgeSize)
+
+ override fun source(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation,
+ ): SceneContainerArea.Resolved {
+ val fixedEdge = fixedEdgeDetector.source(layoutSize, position, density, orientation)
+ return when (fixedEdge) {
+ Edge.Resolved.Left -> SceneContainerArea.Resolved.LeftEdge
+ Edge.Resolved.Bottom -> SceneContainerArea.Resolved.BottomEdge
+ Edge.Resolved.Right -> SceneContainerArea.Resolved.RightEdge
+ else -> {
+ // Note: This intentionally includes Edge.Resolved.Top. At the moment, we don't need
+ // to detect swipes on the top edge, and consider them part of the right/left half.
+ if (position.x < layoutSize.width * 0.5f) {
+ SceneContainerArea.Resolved.LeftHalf
+ } else {
+ SceneContainerArea.Resolved.RightHalf
+ }
+ }
+ }
+ }
+}
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 498ad1d5e49a..01bcc2400933 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
@@ -19,6 +19,7 @@ package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
import android.view.View
import androidx.compose.runtime.getValue
+import androidx.compose.ui.unit.dp
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
@@ -64,7 +65,6 @@ constructor(
private val powerInteractor: PowerInteractor,
shadeModeInteractor: ShadeModeInteractor,
private val remoteInputInteractor: RemoteInputInteractor,
- private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
val lightRevealScrim: LightRevealScrimViewModel,
@@ -89,16 +89,20 @@ constructor(
val hapticsViewModel: SceneContainerHapticsViewModel = hapticsViewModelFactory.create(view)
/**
- * The [SwipeSourceDetector] to use for defining which edges of the screen can be defined in the
+ * The [SwipeSourceDetector] to use for defining which areas of the screen can be defined in the
* [UserAction]s for this container.
*/
- val edgeDetector: SwipeSourceDetector by
+ val swipeSourceDetector: SwipeSourceDetector by
hydrator.hydratedStateOf(
- traceName = "edgeDetector",
+ traceName = "swipeSourceDetector",
initialValue = DefaultEdgeDetector,
source =
shadeModeInteractor.shadeMode.map {
- if (it is ShadeMode.Dual) splitEdgeDetector else DefaultEdgeDetector
+ if (it is ShadeMode.Dual) {
+ SceneContainerSwipeDetector(edgeSize = 40.dp)
+ } else {
+ DefaultEdgeDetector
+ }
},
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetector.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetector.kt
deleted file mode 100644
index f88bcb57a27d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetector.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.ui.viewmodel
-
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.FixedSizeEdgeDetector
-import com.android.compose.animation.scene.SwipeSource
-import com.android.compose.animation.scene.SwipeSourceDetector
-
-/**
- * The edge of a [SceneContainer]. It differs from a standard [Edge] by splitting the top edge into
- * top-left and top-right.
- */
-enum class SceneContainerEdge(private val resolveEdge: (LayoutDirection) -> Resolved) :
- SwipeSource {
- TopLeft(resolveEdge = { Resolved.TopLeft }),
- TopRight(resolveEdge = { Resolved.TopRight }),
- TopStart(
- resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.TopLeft else Resolved.TopRight }
- ),
- TopEnd(
- resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.TopRight else Resolved.TopLeft }
- ),
- Bottom(resolveEdge = { Resolved.Bottom }),
- Left(resolveEdge = { Resolved.Left }),
- Right(resolveEdge = { Resolved.Right }),
- Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
- End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
-
- override fun resolve(layoutDirection: LayoutDirection): Resolved {
- return resolveEdge(layoutDirection)
- }
-
- enum class Resolved : SwipeSource.Resolved {
- TopLeft,
- TopRight,
- Bottom,
- Left,
- Right,
- }
-}
-
-/**
- * A [SwipeSourceDetector] that detects edges similarly to [FixedSizeEdgeDetector], except that the
- * top edge is split in two: top-left and top-right. The split point between the two is dynamic and
- * may change during runtime.
- *
- * Callers who need to detect the start and end edges based on the layout direction (LTR vs RTL)
- * should subscribe to [SceneContainerEdge.TopStart] and [SceneContainerEdge.TopEnd] instead. These
- * will be resolved at runtime to [SceneContainerEdge.Resolved.TopLeft] and
- * [SceneContainerEdge.Resolved.TopRight] appropriately. Similarly, [SceneContainerEdge.Start] and
- * [SceneContainerEdge.End] will be resolved appropriately to [SceneContainerEdge.Resolved.Left] and
- * [SceneContainerEdge.Resolved.Right].
- *
- * @param topEdgeSplitFraction A function which returns the fraction between [0..1] (i.e.,
- * percentage) of screen width to consider the split point between "top-left" and "top-right"
- * edges. It is called on each source detection event.
- * @param edgeSize The fixed size of each edge.
- */
-class SplitEdgeDetector(
- val topEdgeSplitFraction: () -> Float,
- val edgeSize: Dp,
-) : SwipeSourceDetector {
-
- private val fixedEdgeDetector = FixedSizeEdgeDetector(edgeSize)
-
- override fun source(
- layoutSize: IntSize,
- position: IntOffset,
- density: Density,
- orientation: Orientation,
- ): SceneContainerEdge.Resolved? {
- val fixedEdge =
- fixedEdgeDetector.source(
- layoutSize,
- position,
- density,
- orientation,
- )
- return when (fixedEdge) {
- Edge.Resolved.Top -> {
- val topEdgeSplitFraction = topEdgeSplitFraction()
- require(topEdgeSplitFraction in 0f..1f) {
- "topEdgeSplitFraction must return a value between 0.0 and 1.0"
- }
- val isLeftSide = position.x < layoutSize.width * topEdgeSplitFraction
- if (isLeftSide) SceneContainerEdge.Resolved.TopLeft
- else SceneContainerEdge.Resolved.TopRight
- }
- Edge.Resolved.Left -> SceneContainerEdge.Resolved.Left
- Edge.Resolved.Bottom -> SceneContainerEdge.Resolved.Bottom
- Edge.Resolved.Right -> SceneContainerEdge.Resolved.Right
- null -> null
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index b155ada87efd..1f534a5c191a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -111,10 +111,7 @@ constructor(
statusbarWidth: Int,
): ShadeElement {
val xPercentage = motionEvent.x / statusbarWidth
- val threshold = shadeInteractor.get().getTopEdgeSplitFraction()
- return if (xPercentage < threshold) {
- notificationElement.get()
- } else qsShadeElement.get()
+ return if (xPercentage < 0.5f) notificationElement.get() else qsShadeElement.get()
}
private fun monitorDisplayRemovals(): Job {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index c8ce316c41dd..6d68796454eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade.domain.interactor
-import androidx.annotation.FloatRange
import com.android.compose.animation.scene.TransitionKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -66,16 +65,6 @@ interface ShadeInteractor : BaseShadeInteractor {
* wide as the entire screen.
*/
val isShadeLayoutWide: StateFlow<Boolean>
-
- /**
- * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
- * between "top-left" and "top-right" for the purposes of dual-shade invocation.
- *
- * Note that this fraction only determines the *split* between the absolute left and right
- * directions. In RTL layouts, the "top-start" edge will resolve to "top-right", and "top-end"
- * will resolve to "top-left".
- */
- @FloatRange(from = 0.0, to = 1.0) fun getTopEdgeSplitFraction(): Float
}
/** ShadeInteractor methods with implementations that differ between non-empty impls. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index b1129a94d833..77e6a833c153 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -48,8 +48,6 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
- override fun getTopEdgeSplitFraction(): Float = 0.5f
-
override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index c6752f867183..cf3b08c041be 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -20,10 +20,11 @@ import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the single shade. */
fun singleShadeActions(
@@ -66,11 +67,10 @@ fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val notifShadeUserActionResult = UserActionResult.ShowOverlay(Overlays.NotificationsShade)
- val qsShadeuserActionResult = UserActionResult.ShowOverlay(Overlays.QuickSettingsShade)
return arrayOf(
- Swipe.Down to notifShadeUserActionResult,
- Swipe.Down(fromSource = SceneContainerEdge.TopRight) to qsShadeuserActionResult,
+ Swipe.Down to ShowOverlay(Overlays.NotificationsShade),
+ Swipe.Down(fromSource = SceneContainerArea.EndHalf) to
+ ShowOverlay(Overlays.QuickSettingsShade),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 825e0143800b..f0350acd83ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -20,7 +20,6 @@ import com.android.systemui.scene.ui.FakeOverlay
import com.android.systemui.scene.ui.composable.ConstantSceneContainerTransitionsBuilder
import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
@@ -99,7 +98,6 @@ val Kosmos.sceneContainerViewModelFactory by Fixture {
powerInteractor = powerInteractor,
shadeModeInteractor = shadeModeInteractor,
remoteInputInteractor = remoteInputInteractor,
- splitEdgeDetector = splitEdgeDetector,
logger = sceneLogger,
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorKosmos.kt
deleted file mode 100644
index e0b529261c4d..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/viewmodel/SplitEdgeDetectorKosmos.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.ui.viewmodel
-
-import androidx.compose.ui.unit.dp
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-
-var Kosmos.splitEdgeDetector: SplitEdgeDetector by
- Kosmos.Fixture {
- SplitEdgeDetector(
- topEdgeSplitFraction = shadeInteractor::getTopEdgeSplitFraction,
- edgeSize = 40.dp,
- )
- }