summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt26
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt45
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt80
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt112
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt90
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt69
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt141
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt28
11 files changed, 727 insertions, 0 deletions
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt
new file mode 100644
index 000000000000..18c9513acf2a
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.shared.page
+
+import com.android.systemui.scene.shared.model.Scene
+import dagger.Module
+import dagger.multibindings.Multibinds
+
+@Module
+interface SceneModule {
+ @Multibinds fun scenes(): Set<Scene>
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
new file mode 100644
index 000000000000..530706e47dcc
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.composable
+
+import com.android.systemui.bouncer.ui.composable.BouncerScene
+import com.android.systemui.keyguard.ui.composable.LockScreenScene
+import com.android.systemui.qs.ui.composable.QuickSettingsScene
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.shade.ui.composable.ShadeScene
+import dagger.Module
+import dagger.Provides
+
+@Module
+object SceneModule {
+ @Provides
+ fun scenes(
+ bouncer: BouncerScene,
+ gone: GoneScene,
+ lockScreen: LockScreenScene,
+ qs: QuickSettingsScene,
+ shade: ShadeScene,
+ ): Set<Scene> {
+ return setOf(
+ bouncer,
+ gone,
+ lockScreen,
+ qs,
+ shade,
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
new file mode 100644
index 000000000000..853300cf9508
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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.bouncer.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
+@SysUISingleton
+class BouncerScene
+@Inject
+constructor(
+ private val viewModel: BouncerViewModel,
+) : ComposableScene {
+ override val key = SceneKey.Bouncer
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Back to SceneModel(SceneKey.LockScreen),
+ )
+ )
+ .asStateFlow()
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ // TODO(b/280877228): implement the real UI.
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Bouncer", style = MaterialTheme.typography.headlineLarge)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(onClick = { viewModel.onAuthenticateButtonClicked() }) {
+ Text("Authenticate")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt
new file mode 100644
index 000000000000..ad33eb518f91
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockScreenScene.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.viewmodel.LockScreenSceneViewModel
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** The lock screen scene shows when the device is locked. */
+@SysUISingleton
+class LockScreenScene
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val viewModel: LockScreenSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.LockScreen
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ viewModel.upDestinationSceneKey
+ .map { pageKey -> destinationScenes(up = pageKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
+ )
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ LockScreenScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+
+ private fun destinationScenes(
+ up: SceneKey,
+ ): Map<UserAction, SceneModel> {
+ return mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(up),
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
+ )
+ }
+}
+
+@Composable
+private fun LockScreenScene(
+ viewModel: LockScreenSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280879610): implement the real UI.
+
+ val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState()
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Lock screen", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) }
+
+ Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
new file mode 100644
index 000000000000..130395a38512
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 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.qs.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
+@SysUISingleton
+class QuickSettingsScene
+@Inject
+constructor(
+ private val viewModel: QuickSettingsSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.QuickSettings
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+ )
+ )
+ .asStateFlow()
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ QuickSettingsScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+}
+
+@Composable
+private fun QuickSettingsScene(
+ viewModel: QuickSettingsSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280887232): implement the real UI.
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
new file mode 100644
index 000000000000..a21366695f66
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.scene.shared.model.Scene
+
+/** Compose-capable extension of [Scene]. */
+interface ComposableScene : Scene {
+ @Composable fun Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
new file mode 100644
index 000000000000..007055221691
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.composable
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
+ * content from the scene framework.
+ */
+@SysUISingleton
+class GoneScene @Inject constructor() : ComposableScene {
+ override val key = SceneKey.Gone
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
+ )
+ )
+ .asStateFlow()
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ /*
+ * TODO(b/279501596): once we start testing with the real Content Dynamics Framework,
+ * replace this with an error to make sure it doesn't get rendered.
+ */
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Gone", style = MaterialTheme.typography.headlineMedium)
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000000..f8a73d5294bc
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalAnimationApi::class)
+
+package com.android.systemui.scene.ui.composable
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.util.Locale
+
+/**
+ * Renders a container of a collection of "scenes" that the user can switch between using certain
+ * user actions (for instance, swiping up and down) or that can be switched automatically based on
+ * application business logic in response to certain events (for example, the device unlocking).
+ *
+ * It's possible for the application to host several such scene containers, the configuration system
+ * allows configuring each container with its own set of scenes. Scenes can be present in multiple
+ * containers.
+ *
+ * @param viewModel The UI state holder for this container.
+ * @param sceneByKey Mapping of [ComposableScene] by [SceneKey], ordered by z-order such that the
+ * last scene is rendered on top of all other scenes. It's critical that this map contains exactly
+ * and only the scenes on this container. In other words: (a) there should be no scene in this map
+ * that is not in the configuration for this container and (b) all scenes in the configuration
+ * must have entries in this map.
+ * @param modifier A modifier.
+ */
+@Composable
+fun SceneContainer(
+ viewModel: SceneContainerViewModel,
+ sceneByKey: Map<SceneKey, ComposableScene>,
+ modifier: Modifier = Modifier,
+) {
+ val currentScene: SceneModel by viewModel.currentScene.collectAsState()
+
+ AnimatedContent(
+ targetState = currentScene.key,
+ label = "scene container",
+ modifier = modifier,
+ ) { currentSceneKey ->
+ sceneByKey.forEach { (key, composableScene) ->
+ if (key == currentSceneKey) {
+ Scene(
+ scene = composableScene,
+ onSceneChanged = viewModel::setCurrentScene,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
+ }
+}
+
+/** Renders the given [ComposableScene]. */
+@Composable
+private fun Scene(
+ scene: ComposableScene,
+ onSceneChanged: (SceneModel) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress.
+ Box(modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center),
+ ) {
+ scene.Content(
+ modifier = Modifier,
+ )
+
+ val destinationScenes: Map<UserAction, SceneModel> by
+ scene.destinationScenes().collectAsState()
+ val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
+ val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
+ val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
+ val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)]
+ val backDestinationScene = destinationScenes[UserAction.Back]
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged)
+ }
+
+ if (backDestinationScene != null) {
+ BackHandler { onSceneChanged.invoke(backDestinationScene) }
+ }
+ }
+ }
+}
+
+@Composable
+private fun DirectionalButton(
+ direction: Direction,
+ destinationScene: SceneModel?,
+ onSceneChanged: (SceneModel) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Button(
+ onClick = { destinationScene?.let { onSceneChanged.invoke(it) } },
+ enabled = destinationScene != null,
+ modifier = modifier,
+ ) {
+ Text(direction.name.lowercase(Locale.getDefault()))
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
new file mode 100644
index 000000000000..5a092041df93
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.shade.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
+@SysUISingleton
+class ShadeScene
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val viewModel: ShadeSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.Shade
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ viewModel.upDestinationSceneKey
+ .map { sceneKey -> destinationScenes(up = sceneKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
+ )
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ ShadeScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+
+ private fun destinationScenes(
+ up: SceneKey,
+ ): Map<UserAction, SceneModel> {
+ return mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(up),
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.QuickSettings),
+ )
+ }
+}
+
+@Composable
+private fun ShadeScene(
+ viewModel: ShadeSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280887022): implement the real UI.
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Shade", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(
+ onClick = { viewModel.onContentClicked() },
+ ) {
+ Text("Open some content")
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f68bd49230d9..2262d8ab2000 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -40,6 +40,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
+import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -103,6 +104,7 @@ import javax.inject.Named;
QSModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
+ SceneContainerFrameworkModule.class,
StatusBarEventsModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
new file mode 100644
index 000000000000..0ed8b21c100e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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
+
+import com.android.systemui.scene.shared.page.SceneModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ SceneModule::class,
+ ],
+)
+object SceneContainerFrameworkModule