1# Status Bar Data Pipeline 2 3## Background 4 5The status bar is the UI shown at the top of the user's screen that gives them 6information about the time, notifications, and system status like mobile 7conectivity and battery level. This document is about the implementation of the 8wifi and mobile system icons on the right side: 9 10![image of status bar](status-bar.png) 11 12In Android U, the data pipeline that determines what mobile and wifi icons to 13show in the status bar has been re-written with a new architecture. This format 14generally follows Android best practices to 15[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch). 16This document serves as a guide for the new architecture, and as a guide for how 17OEMs can add customizations to the new architecture. 18 19## Architecture 20 21In the new architecture, there is a separate pipeline for each type of icon. For 22Android U, **only the wifi icon and mobile icons have been implemented in the 23new architecture**. 24 25As shown in the Android best practices guide, each new pipeline has a data 26layer, a domain layer, and a UI layer: 27 28![diagram of UI, domain, and data layers](modern-architecture.png) 29 30The classes in the data layer are `repository` instances. The classes in the 31domain layer are `interactor` instances. The classes in the UI layer are 32`viewmodel` instances and `viewbinder` instances. In this document, "repository" 33and "data layer" will be used interchangably (and the same goes for the other 34layers). 35 36The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in 37`statusbar/pipeline/mobile`. 38 39#### Repository (data layer) 40 41System callbacks, broadcast receivers, configuration values are all defined 42here, and exposed through the appropriate interface. Where appropriate, we 43define `Model` objects at this layer so that clients do not have to rely on 44system-defined interfaces. 45 46#### Interactor (domain layer) 47 48Here is where we define the business logic and transform the data layer objects 49into something consumable by the ViewModel classes. For example, 50`MobileIconsInteractor` defines the CBRS filtering logic by exposing a 51`filteredSubscriptions` list. 52 53#### ViewModel (UI layer) 54 55View models should define the final piece of business logic mapping to UI logic. 56For example, the mobile view model checks the `IconInteractor.isRoaming` flow to 57decide whether or not to show the roaming indicator. 58 59#### ViewBinder 60 61These have already been implemented and configured. ViewBinders replace the old 62`applyMobileState` mechanism that existed in the `IconManager` classes of the 63old pipeline. A view binder associates a ViewModel with a View, and keeps the 64view up-to-date with the most recent information from the model. 65 66Any new fields added to the ViewModel classes need to be equivalently bound to 67the view here. 68 69### Putting it all together 70 71Putting that altogether, we have this overall architecture diagram for the 72icons: 73 74![diagram of wifi and mobile pipelines](status-bar-pipeline.png) 75 76### Mobile icons architecture 77 78Because there can be multiple mobile connections at the same time, the mobile 79pipeline is split up hierarchically. At each level (data, domain, and UI), there 80is a singleton parent class that manages information relevant to **all** mobile 81connections, and multiple instances of child classes that manage information for 82a **single** mobile connection. 83 84For example, `MobileConnectionsRepository` is a singleton at the data layer that 85stores information relevant to **all** mobile connections, and it also manages a 86list of child `MobileConnectionRepository` classes. `MobileConnectionRepository` 87is **not** a singleton, and each individual `MobileConnectionRepository` 88instance fully qualifies the state of a **single** connection. This pattern is 89repeated at the `Interactor` and `ViewModel` layers for mobile. 90 91![diagram of mobile parent child relationship](status-bar-mobile-pipeline.png) 92 93Note: Since there is at most one wifi connection, the wifi pipeline is not split 94up in the same way. 95 96## Customizations 97 98The new pipeline completely replaces these classes: 99 100* `WifiStatusTracker` 101* `MobileStatusTracker` 102* `NetworkSignalController` and `NetworkSignalControllerImpl` 103* `MobileSignalController` 104* `WifiSignalController` 105* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and 106 `WifiIconState`) 107 108Any customizations in any of these classes will need to be migrated to the new 109pipeline. As a general rule, any change that would have gone into 110`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any 111change for `MobileSignalController` can be done in `MobileConnectionRepository` 112(see above on the relationship between those repositories). 113 114### Sample customization: New service 115 116Some customizations require listening to additional services to get additional 117data. This new architecture makes it easy to add additional services to the 118status bar data pipeline to get icon customizations. 119 120Below is a general guide to how a new service should be added. However, there 121may be exceptions to this guide for specific use cases. 122 1231. In the data layer (`repository` classes), add a new `StateFlow` that listens 124 to the service: 125 126 ```kotlin 127 class MobileConnectionsRepositoryImpl { 128 ... 129 val fooVal: StateFlow<Int> = 130 conflatedCallbackFlow { 131 val callback = object : FooServiceCallback(), FooListener { 132 override fun onFooChanged(foo: Int) { 133 trySend(foo) 134 } 135 } 136 137 fooService.registerCallback(callback) 138 139 awaitClose { fooService.unregisterCallback(callback) } 140 } 141 .stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL) 142 } 143 ``` 144 1451. In the domain layer (`interactor` classes), either use this new flow to 146 process values, or just expose the flow as-is for the UI layer. 147 148 For example, if `bar` should only be true when `foo` is positive: 149 150 ```kotlin 151 class MobileIconsInteractor { 152 ... 153 val bar: StateFlow<Boolean> = 154 mobileConnectionsRepo 155 .mapLatest { foo -> foo > 0 } 156 .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false) 157 } 158 ``` 159 1601. In the UI layer (`viewmodel` classes), update the existing flows to process 161 the new value from the interactor. 162 163 For example, if the icon should be hidden when `bar` is true: 164 165 ```kotlin 166 class MobileIconViewModel { 167 ... 168 iconId: Flow<Int> = combine( 169 iconInteractor.level, 170 iconInteractor.numberOfLevels, 171 iconInteractor.bar, 172 ) { level, numberOfLevels, bar -> 173 if (bar) { 174 null 175 } else { 176 calcIcon(level, numberOfLevels) 177 } 178 } 179 ``` 180 181## Demo mode 182 183SystemUI demo mode is a first-class citizen in the new pipeline. It is 184implemented as an entirely separate repository, 185`DemoMobileConnectionsRepository`. When the system moves into demo mode, the 186implementation of the data layer is switched to the demo repository via the 187`MobileRepositorySwitcher` class. 188 189Because the demo mode repositories implement the same interfaces as the 190production classes, any changes made above will have to be implemented for demo 191mode as well. 192 1931. Following from above, if `fooVal` is added to the 194 `MobileConnectionsRepository` interface: 195 196 ```kotlin 197 class DemoMobileConnectionsRepository { 198 private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE) 199 override val fooVal: StateFlow<Int> = _fooVal.asStateFlow() 200 201 // Process the state. **See below on how to add the command to the CLI** 202 fun processEnabledMobileState(state: Mobile) { 203 ... 204 _fooVal.value = state.fooVal 205 } 206 } 207 ``` 208 2091. (Optional) If you want to enable the command line interface for setting and 210 testing this value in demo mode, you can add parsing logic to 211 `DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`: 212 213 ```kotlin 214 sealed interface FakeNetworkEventModel { 215 data class Mobile( 216 ... 217 // Add new fields here 218 val fooVal: Int? 219 ) 220 } 221 ``` 222 223 ```kotlin 224 class DemoModeMobileConnectionDataSource { 225 // Currently, the demo commands are implemented as an extension function on Bundle 226 private fun Bundle.activeMobileEvent(): Mobile { 227 ... 228 val fooVal = getString("fooVal")?.toInt() 229 return Mobile( 230 ... 231 fooVal = fooVal, 232 ) 233 } 234 } 235 ``` 236 237If step 2 is implemented, then you will be able to pass demo commands via the 238command line: 239 240``` 241adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value> 242``` 243 244## Migration plan 245 246For Android U, the new pipeline will be enabled and default. However, the old 247pipeline code will still be around just in case the new pipeline doesn’t do well 248in the testing phase. 249 250For Android V, the old pipeline will be completely removed and the new pipeline 251will be the one source of truth. 252 253Our ask for OEMs is to default to using the new pipeline in Android U. If there 254are customizations that seem difficult to migrate over to the new pipeline, 255please file a bug with us and we’d be more than happy to consult on the best 256solution. The new pipeline was designed with customizability in mind, so our 257hope is that working the new pipeline will be easier and faster. 258 259Note: The new pipeline currently only supports the wifi and mobile icons. The 260other system status bar icons may be migrated to a similar architecture in the 261future. 262