forked from LeddaZ/frameworks_base
262 lines
9.3 KiB
Markdown
262 lines
9.3 KiB
Markdown
# Status Bar Data Pipeline
|
||
|
||
## Background
|
||
|
||
The status bar is the UI shown at the top of the user's screen that gives them
|
||
information about the time, notifications, and system status like mobile
|
||
conectivity and battery level. This document is about the implementation of the
|
||
wifi and mobile system icons on the right side:
|
||
|
||
![image of status bar](status-bar.png)
|
||
|
||
In Android U, the data pipeline that determines what mobile and wifi icons to
|
||
show in the status bar has been re-written with a new architecture. This format
|
||
generally follows Android best practices to
|
||
[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
|
||
This document serves as a guide for the new architecture, and as a guide for how
|
||
OEMs can add customizations to the new architecture.
|
||
|
||
## Architecture
|
||
|
||
In the new architecture, there is a separate pipeline for each type of icon. For
|
||
Android U, **only the wifi icon and mobile icons have been implemented in the
|
||
new architecture**.
|
||
|
||
As shown in the Android best practices guide, each new pipeline has a data
|
||
layer, a domain layer, and a UI layer:
|
||
|
||
![diagram of UI, domain, and data layers](modern-architecture.png)
|
||
|
||
The classes in the data layer are `repository` instances. The classes in the
|
||
domain layer are `interactor` instances. The classes in the UI layer are
|
||
`viewmodel` instances and `viewbinder` instances. In this document, "repository"
|
||
and "data layer" will be used interchangably (and the same goes for the other
|
||
layers).
|
||
|
||
The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
|
||
`statusbar/pipeline/mobile`.
|
||
|
||
#### Repository (data layer)
|
||
|
||
System callbacks, broadcast receivers, configuration values are all defined
|
||
here, and exposed through the appropriate interface. Where appropriate, we
|
||
define `Model` objects at this layer so that clients do not have to rely on
|
||
system-defined interfaces.
|
||
|
||
#### Interactor (domain layer)
|
||
|
||
Here is where we define the business logic and transform the data layer objects
|
||
into something consumable by the ViewModel classes. For example,
|
||
`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
|
||
`filteredSubscriptions` list.
|
||
|
||
#### ViewModel (UI layer)
|
||
|
||
View models should define the final piece of business logic mapping to UI logic.
|
||
For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
|
||
decide whether or not to show the roaming indicator.
|
||
|
||
#### ViewBinder
|
||
|
||
These have already been implemented and configured. ViewBinders replace the old
|
||
`applyMobileState` mechanism that existed in the `IconManager` classes of the
|
||
old pipeline. A view binder associates a ViewModel with a View, and keeps the
|
||
view up-to-date with the most recent information from the model.
|
||
|
||
Any new fields added to the ViewModel classes need to be equivalently bound to
|
||
the view here.
|
||
|
||
### Putting it all together
|
||
|
||
Putting that altogether, we have this overall architecture diagram for the
|
||
icons:
|
||
|
||
![diagram of wifi and mobile pipelines](status-bar-pipeline.png)
|
||
|
||
### Mobile icons architecture
|
||
|
||
Because there can be multiple mobile connections at the same time, the mobile
|
||
pipeline is split up hierarchically. At each level (data, domain, and UI), there
|
||
is a singleton parent class that manages information relevant to **all** mobile
|
||
connections, and multiple instances of child classes that manage information for
|
||
a **single** mobile connection.
|
||
|
||
For example, `MobileConnectionsRepository` is a singleton at the data layer that
|
||
stores information relevant to **all** mobile connections, and it also manages a
|
||
list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
|
||
is **not** a singleton, and each individual `MobileConnectionRepository`
|
||
instance fully qualifies the state of a **single** connection. This pattern is
|
||
repeated at the `Interactor` and `ViewModel` layers for mobile.
|
||
|
||
![diagram of mobile parent child relationship](status-bar-mobile-pipeline.png)
|
||
|
||
Note: Since there is at most one wifi connection, the wifi pipeline is not split
|
||
up in the same way.
|
||
|
||
## Customizations
|
||
|
||
The new pipeline completely replaces these classes:
|
||
|
||
* `WifiStatusTracker`
|
||
* `MobileStatusTracker`
|
||
* `NetworkSignalController` and `NetworkSignalControllerImpl`
|
||
* `MobileSignalController`
|
||
* `WifiSignalController`
|
||
* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
|
||
`WifiIconState`)
|
||
|
||
Any customizations in any of these classes will need to be migrated to the new
|
||
pipeline. As a general rule, any change that would have gone into
|
||
`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
|
||
change for `MobileSignalController` can be done in `MobileConnectionRepository`
|
||
(see above on the relationship between those repositories).
|
||
|
||
### Sample customization: New service
|
||
|
||
Some customizations require listening to additional services to get additional
|
||
data. This new architecture makes it easy to add additional services to the
|
||
status bar data pipeline to get icon customizations.
|
||
|
||
Below is a general guide to how a new service should be added. However, there
|
||
may be exceptions to this guide for specific use cases.
|
||
|
||
1. In the data layer (`repository` classes), add a new `StateFlow` that listens
|
||
to the service:
|
||
|
||
```kotlin
|
||
class MobileConnectionsRepositoryImpl {
|
||
...
|
||
val fooVal: StateFlow<Int> =
|
||
conflatedCallbackFlow {
|
||
val callback = object : FooServiceCallback(), FooListener {
|
||
override fun onFooChanged(foo: Int) {
|
||
trySend(foo)
|
||
}
|
||
}
|
||
|
||
fooService.registerCallback(callback)
|
||
|
||
awaitClose { fooService.unregisterCallback(callback) }
|
||
}
|
||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
|
||
}
|
||
```
|
||
|
||
1. In the domain layer (`interactor` classes), either use this new flow to
|
||
process values, or just expose the flow as-is for the UI layer.
|
||
|
||
For example, if `bar` should only be true when `foo` is positive:
|
||
|
||
```kotlin
|
||
class MobileIconsInteractor {
|
||
...
|
||
val bar: StateFlow<Boolean> =
|
||
mobileConnectionsRepo
|
||
.mapLatest { foo -> foo > 0 }
|
||
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
|
||
}
|
||
```
|
||
|
||
1. In the UI layer (`viewmodel` classes), update the existing flows to process
|
||
the new value from the interactor.
|
||
|
||
For example, if the icon should be hidden when `bar` is true:
|
||
|
||
```kotlin
|
||
class MobileIconViewModel {
|
||
...
|
||
iconId: Flow<Int> = combine(
|
||
iconInteractor.level,
|
||
iconInteractor.numberOfLevels,
|
||
iconInteractor.bar,
|
||
) { level, numberOfLevels, bar ->
|
||
if (bar) {
|
||
null
|
||
} else {
|
||
calcIcon(level, numberOfLevels)
|
||
}
|
||
}
|
||
```
|
||
|
||
## Demo mode
|
||
|
||
SystemUI demo mode is a first-class citizen in the new pipeline. It is
|
||
implemented as an entirely separate repository,
|
||
`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
|
||
implementation of the data layer is switched to the demo repository via the
|
||
`MobileRepositorySwitcher` class.
|
||
|
||
Because the demo mode repositories implement the same interfaces as the
|
||
production classes, any changes made above will have to be implemented for demo
|
||
mode as well.
|
||
|
||
1. Following from above, if `fooVal` is added to the
|
||
`MobileConnectionsRepository` interface:
|
||
|
||
```kotlin
|
||
class DemoMobileConnectionsRepository {
|
||
private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
|
||
override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
|
||
|
||
// Process the state. **See below on how to add the command to the CLI**
|
||
fun processEnabledMobileState(state: Mobile) {
|
||
...
|
||
_fooVal.value = state.fooVal
|
||
}
|
||
}
|
||
```
|
||
|
||
1. (Optional) If you want to enable the command line interface for setting and
|
||
testing this value in demo mode, you can add parsing logic to
|
||
`DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
|
||
|
||
```kotlin
|
||
sealed interface FakeNetworkEventModel {
|
||
data class Mobile(
|
||
...
|
||
// Add new fields here
|
||
val fooVal: Int?
|
||
)
|
||
}
|
||
```
|
||
|
||
```kotlin
|
||
class DemoModeMobileConnectionDataSource {
|
||
// Currently, the demo commands are implemented as an extension function on Bundle
|
||
private fun Bundle.activeMobileEvent(): Mobile {
|
||
...
|
||
val fooVal = getString("fooVal")?.toInt()
|
||
return Mobile(
|
||
...
|
||
fooVal = fooVal,
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
If step 2 is implemented, then you will be able to pass demo commands via the
|
||
command line:
|
||
|
||
```
|
||
adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
|
||
```
|
||
|
||
## Migration plan
|
||
|
||
For Android U, the new pipeline will be enabled and default. However, the old
|
||
pipeline code will still be around just in case the new pipeline doesn’t do well
|
||
in the testing phase.
|
||
|
||
For Android V, the old pipeline will be completely removed and the new pipeline
|
||
will be the one source of truth.
|
||
|
||
Our ask for OEMs is to default to using the new pipeline in Android U. If there
|
||
are customizations that seem difficult to migrate over to the new pipeline,
|
||
please file a bug with us and we’d be more than happy to consult on the best
|
||
solution. The new pipeline was designed with customizability in mind, so our
|
||
hope is that working the new pipeline will be easier and faster.
|
||
|
||
Note: The new pipeline currently only supports the wifi and mobile icons. The
|
||
other system status bar icons may be migrated to a similar architecture in the
|
||
future.
|