blob: 234c7a032d2e719e1053c349472b43ce014cc374 [file] [log] [blame] [view]
Alejandro Nijamkindea748a2023-12-30 14:07:21 -08001# The Scene Framework
2
3Known internally as "Flexiglass", this framework defines a graph where each node
4is a "scene" and each edge between the scenes is a transition. The scenes are
5the main components of System UI, on phones these are: the lockscreen, bouncer,
6shade, and quick settings panels/views/screens). Each scene is a standalone
7experience.
8
9The **main goal** of the framework is to increase code health by applying
10[Separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns)
11over several dimensions:
12
131. Each scene is a standalone piece of UI; their code doesn't need to concern
14 itself with either transition animations or anything in other scenes. This
15 frees the developer to be able to focus only on the content of the UI for
16 that scene.
172. Transition definitions (which scene leads to which other scene following
18 which user action) are pulled out and separated from the content of the UI.
193. Transition animations (the effects that happen alongside the gradual change
20 from one scene to another) are also pulled out and separated from the
21 content of the UI.
22
Ale Nijamkin1db15082024-01-18 22:15:02 +000023In addition to the above, some of the **secondary goals** are:
24
254. Make **customization easier**: by separating scenes to standalone pieces, it
26becomes possible for variant owners and OEMs to exclude or replace certain scenes
27or to add brand-new scenes.
285. **Enable modularization**: by separating scenes to standalone pieces, it
29becomes possible to break down System UI into smaller codebases, each one of
30which could be built on its own. Note: this isn't part of the scene framework
31itself but is something that can be done more easily once the scene framework
32is in place.
Alejandro Nijamkindea748a2023-12-30 14:07:21 -080033
34## Terminology
35
36* **Scene** a collection of UI elements in a layout that, together, make up a
37 "screen" or "page" that is as large as the container. Scenes can be
38 navigated between / transition to/from. To learn more, please see
39 [this section](#Defining-a-scene).
40* **Element** (or "UI element") a single unit of UI within a scene. One scene
41 can arrange multiple elements within a layout structure.
42* **Transition** the gradual switching from one scene to another scene. There
43 are two kinds: [user-driven](Scene-navigation) and
44 [automatic](Automatic-scene-transitions) scene transitions.
45* **Transition animation** the set of UI effects that occurs while/during a
46 transition. These can apply to the entire scene or to specific elements in
47 the scene. To learn more, please see
48 [this section](#Scene-transition-animations).
49* **Scene container** (or just "container") the root piece of UI (typically a
50 `@Composable` function) that sets up all the scenes, their transitions, etc.
51 To learn more, please see [this section](#Scene-container).
52* **Container configuration** (or just "configuration") the collection of
53 scenes and some added information about the desired behaviour of a
54 container. To learn more, please see
55 [this section](#Scene-container-configuration).
56
57## Enabling the framework
58
59As of the end of 2023, the scene framework is under development; as such, it is
60disabled by default. For those who are interested in a preview, please follow
61the instructions below to turn it on.
62
63NOTE: in case these instructions become stale and don't actually enable the
64framework, please make sure `SceneContainerFlag.isEnabled` in the
Alejandro Nijamkin63f3c7b2024-04-30 14:44:45 -070065[`SceneContainerFlag.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt)
Alejandro Nijamkindea748a2023-12-30 14:07:21 -080066file evalutes to `true`.
67
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +0000681. Set a collection of **aconfig flags** to `true` by running the following
Ale Nijamkin1db15082024-01-18 22:15:02 +000069 commands:
70 ```console
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +000071 $ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true
72 $ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +000073 $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true
Ale Nijamkinf4296f12024-09-17 17:29:08 +000074 $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +000075 $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true
76 $ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true
Ale Nijamkinf4296f12024-09-17 17:29:08 +000077 $ adb shell device_config override systemui com.android.systemui.scene_container true
Ale Nijamkin1db15082024-01-18 22:15:02 +000078 ```
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +0000792. **Restart** System UI by issuing the following command:
Ale Nijamkin1db15082024-01-18 22:15:02 +000080 ```console
81 $ adb shell am crash com.android.systemui
82 ```
Caitlin Shkuratovf12f7132024-04-24 19:08:41 +0000833. **Verify** that the scene framework was turned on. There are two ways to do
Alejandro Nijamkindea748a2023-12-30 14:07:21 -080084 this:
85
86 *(a)* look for the sash/ribbon UI at the bottom-right corner of the display:
87 ![ribbon](imgs/ribbon.png)
88
89 NOTE: this will be removed proper to the actual release of the framework.
90
91 *(b)* Turn on logging and look for the logging statements in `logcat`:
92 ```console
93
94 # Turn on logging from the framework:
95
96 $ adb shell cmd statusbar echo -b SceneFramework:verbose
97
Alejandro Nijamkin83f19722024-01-18 15:17:57 -080098### Checking if the framework is enabled
99
100Look for the log statements from the framework:
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800101
Ale Nijamkin1db15082024-01-18 22:15:02 +0000102```console
103$ adb logcat -v time SceneFramework:* *:S
104```
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800105
Alejandro Nijamkin83f19722024-01-18 15:17:57 -0800106### Disabling the framework
107
Ale Nijamkin1db15082024-01-18 22:15:02 +0000108To **disable** the framework, simply turn off the main aconfig flag:
109
110```console
111$ adb shell device_config put systemui com.android.systemui.scene_container false
112```
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800113
114## Defining a scene
115
Alejandro Nijamkin83f19722024-01-18 15:17:57 -0800116By default, the framework ships with fully functional scenes as enumarated
117[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt).
118Should a variant owner or OEM want to replace or add a new scene, they could
119do so by defining their own scene. This section describes how to do that.
120
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800121Each scene is defined as an implementation of the
burakova398c982024-09-02 12:22:58 +0000122[`Scene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt)
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800123interface, which has three parts: 1. The `key` property returns the
124[`SceneKey`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt)
burakov2f383802024-09-02 13:31:17 +0000125that uniquely identifies that scene 2. The `userActions` `Flow` returns
126the (potentially ever-changing) set of navigation edges to other content, based
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800127on user-actions, which is how the navigation graph is defined (see
128[the Scene navigation](#Scene-navigation) section for more) 3. The `Content`
129function which uses
130[Jetpack Compose](https://developer.android.com/jetpack/compose) to declare of
131the UI itself. This is the UI "at rest", e.g. once there is no transition
132between any two scenes. The Scene Framework has other ways to define how the
133content of your UI changes with and throughout a transition to learn more please
134see the [Scene transition animations](#Scene-transition-animations) section
135
Ale Nijamkin1db15082024-01-18 22:15:02 +0000136For example:
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800137
Ale Nijamkin1db15082024-01-18 22:15:02 +0000138```kotlin
burakova398c982024-09-02 12:22:58 +0000139@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : Scene {
Ale Nijamkin1db15082024-01-18 22:15:02 +0000140 override val key = SceneKey.YourScene
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800141
burakov2f383802024-09-02 13:31:17 +0000142 override val userActions: StateFlow<Map<UserAction, SceneModel>> =
Ale Nijamkin1db15082024-01-18 22:15:02 +0000143 MutableStateFlow<Map<UserAction, SceneModel>>(
144 mapOf(
145 // This is where scene navigation is defined, more on that below.
146 )
147 ).asStateFlow()
148
149 @Composable
150 override fun SceneScope.Content(
151 modifier: Modifier,
152 ) {
153 // This is where the UI is defined using Jetpack Compose.
154 }
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800155}
156```
157
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800158### Injecting scenes
159
160Scenes are injected into the Dagger dependency graph from the
161[`SceneModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt;l=35-50;drc=564f233d5b597aedf06961c76e582464eebe8ba6).
162
163## Scene navigation
164
165As seen above, each scene is responsible for providing an observable `Flow` of a
166`Map` that connects `UserAction` (for example: swipe down, swipe up, back
167button/gesture, etc.) keys to `SceneModel` destinations. This is how the scene
168navigation graph is defined.
169
170NOTE: this controls *only* user-input based navigation. To learn about the other
171type of scene navigation, please see the
172[Automatic scene transitions](#Automatic-scene-transitions) section.
173
174Because this is a `Flow`, scene implemetations should feel free to emit new
175values over time. For example, the `Lockscreen` scene ties the "swipe up" user
176action to go to the `Bouncer` scene if the device is still locked or to go to
177the `Gone` scene if the device is unlocked, allowing the user to dismiss the
178lockscreen UI when not locked.
179
180## Scene transition animations
181
182The Scene Framework separates transition animations from content UI declaration
183by placing the definition of the former in a different location. This way,
184there's no longer a need to contaminate the content UI declaration with
185animation logic, a practice that becomes unscalable over time.
186
187Under the hood, the Scene Framework uses
188[`SceneTransitionLayout`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt),
189a `@Composable` function designed with scene graph and transitions in mind. In
190fact, the Scene Framework is merely a shallow wrapper around
191`SceneTransitionLayout`.
192
193The `SceneTransitionLayout` API requires the transitions to be passed-in
194separately from the scenes themselves. In System UI, the transitions can be
195found in
196[`SceneContainerTransitions`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt).
197As you can see, each possible scene-to-scene transition has its own builder,
198here's one example:
199
200```kotlin
201fun TransitionBuilder.lockscreenToShadeTransition() {
202 spec = tween(durationMillis = 500)
203
204 punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
205 translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
206 fractionRange(end = 0.5f) {
207 fade(Shade.Elements.ScrimBackground)
208 translate(
209 QuickSettings.Elements.CollapsedGrid,
210 Edge.Top,
211 startsOutsideLayoutBounds = false,
212 )
213 }
214 fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
215}
216```
217
Ale Nijamkin1db15082024-01-18 22:15:02 +0000218Going through the example code:
219
220* The `spec` is the animation that should be invoked, in the example above, we use a `tween`
221animation with a duration of 500 milliseconds
222* Then there's a series of function calls: `punchHole` applies a clip mask to the `Scrim`
223element in the destination scene (in this case it's the `Shade` scene) which has the
224position and size determined by the `bounds` parameter and the shape passed into the `shape`
225parameter. This lets the `Lockscreen` scene render "through" the `Shade` scene
226* The `translate` call shifts the `Scrim` element to/from the `Top` edge of the scene container
227* The first `fractionRange` wrapper tells the system to apply its contained functions
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800228only during the first half of the transition. Inside of it, we see a `fade` of
229the `ScrimBackground` element and a `translate` o the `CollpasedGrid` element
Ale Nijamkin1db15082024-01-18 22:15:02 +0000230to/from the `Top` edge
231* The second `fractionRange` only starts at the second half of the transition (e.g. when
232the previous one ends) and applies a `fade` on the `Notifications` element
Alejandro Nijamkindea748a2023-12-30 14:07:21 -0800233
234You can find the actual documentation for this API
235[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt).
236
237### Tagging elements
238
239As demonstrated above, elements within a scene can be addressed from transition
240defintions. In order to "tag" an element with a specific `ElementKey`, the
241[`element` modifier](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt)
242must be used on the composable that declared that element's UI:
243
244```kotlin
245Text(
246 text = "Some text",
247 modifier = Modifier.element(MyElements.SomeText),
248)
249```
250
251In addition to the ability to refer to a tagged element in transition
252definitions, if the same `ElementKey` is used for one element in the current
253scene and another element in the destination scene, the element is considered to
254be a **shared element**. As such, the framework automatically translates and
255scales the bounds of the shared element from its current bounds in the source
256scene to its final bounds in the destination scene.
257
258## Scene container
259
260To set up a scene framework instance, a scene container must be declared. This
261is the root of an entire scene graph that puts together the scenes, their
262transitions, and the configuration. The container is then added to a parent
263`@Composable` or `View` so it can be displayed.
264
265The default scene container in System UI is defined in the
266[`SceneContainer.kt` file](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt).
267
268### Scene container configuration
269
270The `SceneContainer` function is passed a few parameters including a view-model
271and a set of scenes. The exact details of what gets passed in depends on the
272[`SceneContainerConfig` object](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt)
273which is injected into the Dagger dependency graph
274[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt).
275
276## Automatic scene transitions
277
278The scene framework supports the ability for scenes to change automatically
279based on device state or events other than direct user input. For example: when
280the device is locked, there's an automatic scene transition to the `Lockscreen`
281scene.
282
283This logic is contained within the
284[`SceneContainerStartable`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt)
285class.
286
287## Side-effects
288
289Similarly to [the above](#Automatic-scene-transitions), the
290`SceneContainerStartable` also handles side-effects by updating other parts of
291the System UI codebase whenever internal scene framework state changes. As an
292example: the visibility of the `View` that contains our
293[scene container](#Scene-container) is updated every time there's a transition
294to or from the `Gone` scene.
295
296## Observing scene transition state
297
298There are a couple of ways to observe the transition state:
299
3001. [Easiest] using the `SceneScope` of the scene container, simply use the
301 `animateSharedXAsState` API, the full list is
302 [here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt).
3032. [Harder] if outside the `SceneScope` of the scene container, observe
304 [`SceneInteractor.transitionState`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt;l=88;drc=af57d5e49431c6728e7cf192bada88e0541ebf0c).
305
306## Dependency Injection
307
308The entire framework is provided into the Dagger dependency graph from the
309top-level Dagger module at
310[`SceneContainerFrameworkModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt)
311this puts together the scenes from `SceneModule`, the configuration from
312`SceneContainerConfigModule`, and the startable from
313`SceneContainerStartableModule`.
Alejandro Nijamkin83f19722024-01-18 15:17:57 -0800314
315## Integration Notes
316
317### Relationship to Jetpack Compose
318
319The scene framework depends on Jetpack Compose; therefore, compiling System UI with
320Jetpack Compose is required. However, because Jetpack Compose and Android Views
321[interoperate](https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose),
322the UI in each scene doesn't necessarily need to be a pure hierarchy of `@Composable`
323functions; instead, it's acceptable to use an `AndroidView` somewhere in the
324hierarchy of composable functions to include a `View` or `ViewGroup` subtree.
325
326#### Interoperability with Views
327The scene framework comes with built-in functionality to animate the entire scene and/or
328elements within the scene in-tandem with the actual scene transition progress.
329
330For example, as the user drags their finger down rom the top of the lockscreen,
331the shade scene becomes visible and gradually expands, the amount of expansion tracks
332the movement of the finger.
333
334That feature of the framework uses a custom `element(ElementKey)` Jetpack Compose
335`Modifier` to refer to elements within a scene.
336The transition builders then use the same `ElementKey` objects to refer to those elements
337and describe how they animate in-tandem with scene transitions. Because this is a
338Jetpack Compose `Modifier`, it means that, in order for an element in a scene to be
339animated automatically by the framework, that element must be nested within a pure
340`@Composable` hierarchy. The element itself is allowed to be a classic Android `View`
341(nested within a Jetpack Compose `AndroidView`) but all ancestors must be `@Composable`
342functions.
343
344### Notifications
345
346As of January 2024, the integration of notifications and heads-up notifications (HUNs)
347into the scene framework follows an unusual pattern. We chose this pattern due to migration
348risk and performance concerns but will eventually replace it with the more common element
349placement pattern that all other elements are following.
350
351The special pattern for notifications is that, instead of the notification list
352(`NotificationStackScrollLayout` or "NSSL", which also displays HUNs) being placed in the element
353hierarchy within the scenes that display notifications, the NSSL (which continues to be an Android View)
354"floats" above the scene container, rendering on top of everything. This is very similar to
355how NSSL is integrated with the legacy shade, prior to the scene framework.
356
357In order to render the NSSL as if it's part of the organic hierarchy of elements within its
358scenes, we control the NSSL's self-imposed effective bounds (e.g. position offsets, clip path,
359size) from `@Composable` elements within the normal scene hierarchy. These special
360"placeholder" elements can be found
361[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt).
362