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:
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. This document serves as a guide for the new architecture, and as a guide for how OEMs can add customizations to the new 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:
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
.
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.
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.
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.
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 that altogether, we have this overall architecture diagram for the icons:
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.
Note: Since there is at most one wifi connection, the wifi pipeline is not split up in the same way.
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).
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.
In the data layer (repository
classes), add a new StateFlow
that listens to the service:
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) }
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:
class MobileIconsInteractor { ... val bar: StateFlow<Boolean> = mobileConnectionsRepo .mapLatest { foo -> foo > 0 } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false) }
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:
class MobileIconViewModel { ... iconId: Flow<Int> = combine( iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.bar, ) { level, numberOfLevels, bar -> if (bar) { null } else { calcIcon(level, numberOfLevels) } }
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.
Following from above, if fooVal
is added to the MobileConnectionsRepository
interface:
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 } }
(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
:
sealed interface FakeNetworkEventModel { data class Mobile( ... // Add new fields here val fooVal: Int? ) }
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>
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.