summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Behnam Heydarshahi <bhnm@google.com> 2025-02-12 11:29:25 -0800
committer Behnam Heydarshahi <bhnm@google.com> 2025-02-12 11:29:25 -0800
commitfc42881eaf85cd6be6ff22094e1819e6a87b5b72 (patch)
treebf074b45b7af1f77ab34fa7375dbd4b63d253471
parent77d495abae3d1d93f91b1562413ecc98c8815e3b (diff)
Add info about new tile backend architecture
Flag: DOCS_ONLY Bug: 302178282 Change-Id: I8219122185ab44018611820d1ab988fba6d9c7ae
-rw-r--r--packages/SystemUI/docs/qs-tiles.md167
1 files changed, 131 insertions, 36 deletions
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
index ee388ec8e5c5..87d9b7c3b853 100644
--- a/packages/SystemUI/docs/qs-tiles.md
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -8,6 +8,14 @@ This document is a more or less comprehensive summary of the state and infrastru
Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and
how SystemUI manages and displays tiles, among other topics.
+A lot of the tile backend architecture is in the process of being replaced by a new architecture in
+order to align with the
+[recommended architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+
+While we are in the process of migrating, this document will try to provide a comprehensive
+overview of the current architecture as well as the new one. The sections documenting the new
+architecture are marked with the tag [NEW-ARCH].
+
## What are Quick Settings Tiles?
Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to
@@ -72,6 +80,27 @@ The interfaces in `QSTile` as well as other interfaces described in this documen
implement plugins to add additional tiles or different behavior. For more information,
see [plugins.md](plugins.md)
+
+#### [NEW-ARCH] Tile backend
+Instead of `QSTileImpl` the tile backend is made of a view model called `QSTileViewModelImpl`,
+which in turn is composed of 3 interfaces:
+
+* [`QSTileDataInteractor`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt)
+is responsible for providing the data for the tile. It is responsible for fetching the state of
+the tile from the source of truth and providing that information to the tile. Typically the data
+interactor will read system state from a repository or a controller and provide a flow of
+domain-specific data model.
+
+* [`QSTileUserActionInteractor`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt) is responsible for handling the user actions on the tile.
+This interactor decides what should happen when the user clicks, long clicks on the tile.
+
+* [`QSTileDataToStateMapper`](/packages/SystemUI/src/com/android/systemui/qs/tiles/base/mapper/QSTileMapper.kt)
+is responsible for mapping the data received from the data interactor to a state that the view
+model can use to update the UI.
+
+At the time being, the `QSTileViewModel`s are adapted to `QSTile`. This conversion is done by
+`QSTileViewModelAdapter`.
+
#### Tile State
Each tile has an associated `State` object that is used to communicate information to the
@@ -94,6 +123,11 @@ Additionally. `BooleanState` has a `value` boolean field that usually would be s
to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along
with `expandedAccessibilityClassName`.
+#### [NEW-ARCH] Tile State
+In the new architecture, the mapper generates
+[`QSTileState`](packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt),
+which again is converted to the old state by `QSTileViewModelAdapter`.
+
#### SystemUI tiles
Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common
@@ -103,6 +137,9 @@ to handle different events (refresh, click, etc.).
For more information on how to implement a tile in SystemUI,
see [Implementing a SystemUI tile](#implementing-a-systemui-tile).
+As mentioned before, when the [NEW-ARCH] migration is complete, we will remove the `QSTileImpl`
+and `QSTileViewModelAdapter` and directly use`QSTileViewModelImpl`.
+
### Tile views
Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated
@@ -154,37 +191,24 @@ corresponding `QSTile` with its `QSTileView`, doing the following:
#### Life of a tile click
This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the
-device (for example, changes from Settings) will trigger this process starting in step 3. Throughout
-this section, we assume that we are dealing with a `QSTileImpl`.
-
-1. User clicks on tile. The following calls happen in sequence:
- 1. `QSTileViewImpl#onClickListener`.
- 2. `QSTile#click`.
- 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the
- associated controller.
-2. State in the device changes. This is normally outside of SystemUI's control.
-3. Controller receives a callback (or `Intent`) indicating the change in the device. The following
- calls happen:
- 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the
- new state.
- 2. `QSTileImpl#handleRefreshState`
-4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This
- information can be obtained both from the `Object` passed to `refreshState` as well as from the
- controller.
-5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called.
- This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the
- new `State`.
-6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method
- maps the state into the view:
- * The tile colors change to match the new state.
- * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to
- the view.
- * The tile labels change to match the new state.
+device (for example, changes from Settings) will trigger this process starting in step 5.
+
+Step | Legacy Tiles | [NEW-ARCH] Tiles
+-------|-------|---------
+1 | User clicks on tile. | Same as legacy tiles.
+2 | `QSTileViewImpl#onClickListener` | Same as legacy tiles.
+3 | `QSTile#click` | Same as legacy tiles.
+4| `QSTileImpl#handleClick` | `QSTileUserActionInteractor#handleInput`
+5| State in the device changes. This is normally outside of SystemUI's control. Controller receives a callback (or `Intent`) indicating the change in the device. | Same as legacy tiles.
+6 | `QSTile#refreshState`and `QSTileImpl#handleRefreshState` | `QSTileDataInteractor#tileData()`
+7| `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. | The data that was generated by the data interactor is read by the `QSTileViewModelImpl.state` flow which calls `QSTileMapper#map` on the data to generate a new `QSTileState`.
+8| If the state has changed (in at least one element `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. | The newly mapped QSTileState is read by the `QSTileViewModelAdapter` which then maps it to a legacy `State`. Similarly to the legacy tiles, the new state is compared to the old one and if there is a difference, `QSTile.Callback#onStateChanged` is called for all the associated callbacks.
+9 | `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state updating tile color and label, and calling `QSIconView.setIcon` | Same as legacy tiles.
## Third party tiles (TileService)
-A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This
-is implemented by developers
+A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI).
+This is implemented by developers
subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and
interacting with its API.
@@ -220,9 +244,9 @@ from SystemUI:
* **`onTileAdded`**: called when the tile is added to QS.
* **`onTileRemoved`**: called when the tile is removed from QS.
* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of
- the window when calling `getQSTile` is safe and will provide the correct object.
-* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This
- marks the end of the window described in `onStartListening`.
+the window when calling `getQSTile` is safe and will provide the correct object.
+* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user.
+This marks the end of the window described in `onStartListening`.
* **`onClick`**: called when the user clicks on the tile.
Additionally, the following final methods are provided:
@@ -379,13 +403,14 @@ correct list of tiles.
### QSFactory
+`CurrentTilesInteractorImpl` uses the `QSFactory` interface to create the tiles.
+
This interface provides a way of creating tiles and views from a spec. It can be used in plugins to
provide different definitions for tiles.
-In SystemUI there is only one implementation of this factory and that is the default
-factory (`QSFactoryImpl`) in `CurrentTilesInteractorImpl`.
+In SystemUI there are two implementation of this factory. The first one is `QSFactoryImpl` in used for legacy tiles. The second one is `NewQSFactory` used for [NEW-ARCH] tiles.
-#### QSFactoryImpl
+#### QSFactoryImpl (legacy tiles)
This class implements the following method as specified in the `QSFactory` interface:
@@ -402,6 +427,12 @@ This class implements the following method as specified in the `QSFactory` inter
As part of filtering not valid tiles, custom tiles that don't have a corresponding valid service
component are never instantiated.
+#### NewQSFactory ([NEW-ARCH] tiles)
+
+This class also implements the `createTile` method as specified in the `QSFactory` interface.
+However, it first uses the spec to get a `QSTileViewModel`. The view model is then adapted into a
+`QSTile` using the `QSTileViewModelAdapter`.
+
### Lifecycle of a Tile
We describe first the parts of the lifecycle that are common to SystemUI tiles and third party
@@ -415,7 +446,7 @@ tiles.
2. This updates the flow that `CurrentTilesInteractor` is collecting from, triggering the process
described above.
3. `CurrentTilesInteractor` calls the available `QSFactory` classes in order to find one that will
- be able to create a tile with that spec. Assuming that `QSFactoryImpl` managed to create the
+ be able to create a tile with that spec. Assuming that some factory managed to create the
tile, which is some implementation of `QSTile` (either a SystemUI subclass
of `QSTileImpl` or a `CustomTile`) it will be added to the current list.
If the tile is available, it's stored in a map and things proceed forward.
@@ -452,7 +483,7 @@ in [Third party tiles (TileService)](#third-party-tiles-tileservice).
This section describes necessary and recommended steps when implementing a Quick Settings tile. Some
of them are optional and depend on the requirements of the tile.
-### Implementing a SystemUI tile
+### Implementing a legacy SystemUI tile
1. Create a class (preferably
in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles))
@@ -579,6 +610,70 @@ type variable of type `State`.
Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to
each available tile.
+### Implementing a [NEW-ARCH] SystemUI tile
+In the new system the tiles are created in the path
+[`packages/SystemUI/src/com/android/systemui/qs/tiles/impl/<spec>`](packages/SystemUI/src/com/android/systemui/qs/tiles/impl/<spec>)
+where the `<spec>` should be replaced by the spec of the tile e.g. rotation for `RotationLockTile`.
+
+To create a new tile, the developer needs to implement the following data class and interfaces:
+
+[`DataModel`] is a class that describes the system state of the feature that the tile is trying to
+represent. Let's refer to the type of this class as DATA_TYPE. For example a simplified version of
+the data model for a flashlight tile could be a class with a boolean field that represents
+whether the flashlight is on or not.
+
+This file should be placed in the relative path `domain/model/` down from the tile's package.
+
+[`QSTileDataInteractor`] There are two abstract methods that need to be implemented:
+* `fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>`: This method
+returns a flow of data that will be used to create the state of the tile. This is where the system
+state is listened to and converted to a flow of data model. Avoid loading data or settings up
+listeners on the main thread. The userHandle is the user for which the tile is created.
+The triggers flow is a flow of events that can be used to trigger a refresh of the data.
+The most common triggers the force update and initial request.
+
+* `fun availability(user: UserHandle): Flow<Boolean>`: This method returns a flow of booleans that
+indicates if the tile should be shown or not. This is where the availability of the system feature
+(e.g. wifi) is checked. The userHandle is the user for which the tile is created.
+
+This file should be placed in the relative path `domain/interactor/` down from the tile's package.
+
+[`QSTileUserActionInteractor`]
+* `fun handleInput(input: QSTileInput)` is the method that needs to be implemented. This is the
+method that will be called when the user interacts with the tile. The input parameter contains
+the type of interaction (click, long click, toggle) and the DATA_TYPE of the latest data when the
+input was received.
+
+This file should be placed in the relative path `/domain/interactor` down from the tile's package.
+
+[`QSTileDataToStateMapper`]
+* `fun map(data: DATA_TYPE): QSTileState` is the method that needs to be implemented. This method
+is responsible for mapping the data received from the data interactor to a state that the view
+model can use to update the UI. This is where for example the icon should be loaded, and the
+label and content description set. The map function will run on UIBackground thread, a single
+thread which has higher priority than the background thread and lower than UI thread. Loading a
+resource on UI thread can cause jank by blocking the UI thread. On the other end of the spectrum,
+loading resources using a background dispatcher may cause jank due to background thread contention
+since it is possible for the background dispatcher to use more than one background thread
+at the same time. In contrast, the UIBackground dispatcher uses a single thread that is
+shared by all tiles. Therefore the system will use UIBackground dispatcher to execute
+the map function.
+
+The most important resource to load in the map function is the icon. We prefer `Icon.Loaded` with a
+resource id over `Icon.Resource`, because then (a) we can guarantee that the drawable loading will
+happen on the UIBackground thread and (b) we can cache the drawables using the resource id.
+
+This file should be placed in the relative path `/ui/mapper` down from the tile's package.
+
+#### Testing a [NEW-ARCH] SystemUI tile
+
+When writing tests, the mapper is usually a good place to start, since that is where the
+business logic decisions are being made that can inform the shape of data interactor.
+
+We suggest taking advantage of the existing class `QSTileStateSubject`. So rather than
+asserting an individual field's value, a test will assert the whole state. That can be
+achieved by `QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)`.
+
### Implementing a third party tile
For information about this, use the Android Developer documentation