1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod 18 19 import android.util.IndentingPrintWriter 20 import androidx.annotation.VisibleForTesting 21 import com.android.systemui.dagger.qualifiers.Background 22 import com.android.systemui.log.table.TableLogBuffer 23 import com.android.systemui.log.table.TableLogBufferFactory 24 import com.android.systemui.log.table.logDiffsForTable 25 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 26 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel 27 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 28 import java.io.PrintWriter 29 import javax.inject.Inject 30 import kotlinx.coroutines.CoroutineScope 31 import kotlinx.coroutines.flow.Flow 32 import kotlinx.coroutines.flow.MutableStateFlow 33 import kotlinx.coroutines.flow.SharingStarted 34 import kotlinx.coroutines.flow.StateFlow 35 import kotlinx.coroutines.flow.flatMapLatest 36 import kotlinx.coroutines.flow.mapLatest 37 import kotlinx.coroutines.flow.stateIn 38 39 /** 40 * A repository that fully implements a mobile connection. 41 * 42 * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl] 43 * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository 44 * switches between the two types of connections based on whether the connection is currently 45 * carrier merged (see [setIsCarrierMerged]). 46 */ 47 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 48 class FullMobileConnectionRepository( 49 override val subId: Int, 50 startingIsCarrierMerged: Boolean, 51 override val tableLogBuffer: TableLogBuffer, 52 subscriptionModel: Flow<SubscriptionModel?>, 53 private val defaultNetworkName: NetworkNameModel, 54 private val networkNameSeparator: String, 55 @Background scope: CoroutineScope, 56 private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory, 57 private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory, 58 ) : MobileConnectionRepository { 59 /** 60 * Sets whether this connection is a typical mobile connection or a carrier merged connection. 61 */ 62 fun setIsCarrierMerged(isCarrierMerged: Boolean) { 63 _isCarrierMerged.value = isCarrierMerged 64 } 65 66 /** 67 * Returns true if this repo is currently for a carrier merged connection and false otherwise. 68 */ 69 @VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value 70 71 private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged) 72 private val isCarrierMerged: StateFlow<Boolean> = 73 _isCarrierMerged 74 .logDiffsForTable( 75 tableLogBuffer, 76 columnName = "isCarrierMerged", 77 initialValue = startingIsCarrierMerged, 78 ) 79 .stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged) 80 81 private val mobileRepo: MobileConnectionRepository by lazy { 82 mobileRepoFactory.build( 83 subId, 84 tableLogBuffer, 85 subscriptionModel, 86 defaultNetworkName, 87 networkNameSeparator, 88 ) 89 } 90 91 private val carrierMergedRepo: MobileConnectionRepository by lazy { 92 carrierMergedRepoFactory.build(subId, tableLogBuffer) 93 } 94 95 @VisibleForTesting 96 internal val activeRepo: StateFlow<MobileConnectionRepository> = run { 97 val initial = 98 if (startingIsCarrierMerged) { 99 carrierMergedRepo 100 } else { 101 mobileRepo 102 } 103 104 this.isCarrierMerged 105 .mapLatest { isCarrierMerged -> 106 if (isCarrierMerged) { 107 carrierMergedRepo 108 } else { 109 mobileRepo 110 } 111 } 112 .stateIn(scope, SharingStarted.WhileSubscribed(), initial) 113 } 114 115 override val carrierId = 116 activeRepo 117 .flatMapLatest { it.carrierId } 118 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierId.value) 119 120 override val cdmaRoaming = 121 activeRepo 122 .flatMapLatest { it.cdmaRoaming } 123 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value) 124 125 override val isEmergencyOnly = 126 activeRepo 127 .flatMapLatest { it.isEmergencyOnly } 128 .logDiffsForTable( 129 tableLogBuffer, 130 columnName = COL_EMERGENCY, 131 initialValue = activeRepo.value.isEmergencyOnly.value, 132 ) 133 .stateIn( 134 scope, 135 SharingStarted.WhileSubscribed(), 136 activeRepo.value.isEmergencyOnly.value, 137 ) 138 139 override val isRoaming = 140 activeRepo 141 .flatMapLatest { it.isRoaming } 142 .logDiffsForTable( 143 tableLogBuffer, 144 columnName = COL_ROAMING, 145 initialValue = activeRepo.value.isRoaming.value, 146 ) 147 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value) 148 149 override val operatorAlphaShort = 150 activeRepo 151 .flatMapLatest { it.operatorAlphaShort } 152 .logDiffsForTable( 153 tableLogBuffer, 154 columnName = COL_OPERATOR, 155 initialValue = activeRepo.value.operatorAlphaShort.value, 156 ) 157 .stateIn( 158 scope, 159 SharingStarted.WhileSubscribed(), 160 activeRepo.value.operatorAlphaShort.value, 161 ) 162 163 override val isInService = 164 activeRepo 165 .flatMapLatest { it.isInService } 166 .logDiffsForTable( 167 tableLogBuffer, 168 columnName = COL_IS_IN_SERVICE, 169 initialValue = activeRepo.value.isInService.value, 170 ) 171 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value) 172 173 override val isNonTerrestrial = 174 activeRepo 175 .flatMapLatest { it.isNonTerrestrial } 176 .logDiffsForTable( 177 tableLogBuffer, 178 columnName = COL_IS_NTN, 179 initialValue = activeRepo.value.isNonTerrestrial.value, 180 ) 181 .stateIn( 182 scope, 183 SharingStarted.WhileSubscribed(), 184 activeRepo.value.isNonTerrestrial.value, 185 ) 186 187 override val isGsm = 188 activeRepo 189 .flatMapLatest { it.isGsm } 190 .logDiffsForTable( 191 tableLogBuffer, 192 columnName = COL_IS_GSM, 193 initialValue = activeRepo.value.isGsm.value, 194 ) 195 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value) 196 197 override val cdmaLevel = 198 activeRepo 199 .flatMapLatest { it.cdmaLevel } 200 .logDiffsForTable( 201 tableLogBuffer, 202 columnName = COL_CDMA_LEVEL, 203 initialValue = activeRepo.value.cdmaLevel.value, 204 ) 205 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value) 206 207 override val primaryLevel = 208 activeRepo 209 .flatMapLatest { it.primaryLevel } 210 .logDiffsForTable( 211 tableLogBuffer, 212 columnName = COL_PRIMARY_LEVEL, 213 initialValue = activeRepo.value.primaryLevel.value, 214 ) 215 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value) 216 217 override val satelliteLevel: StateFlow<Int> = 218 activeRepo 219 .flatMapLatest { it.satelliteLevel } 220 .logDiffsForTable( 221 tableLogBuffer, 222 columnName = COL_SATELLITE_LEVEL, 223 initialValue = activeRepo.value.satelliteLevel.value, 224 ) 225 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.satelliteLevel.value) 226 227 override val dataConnectionState = 228 activeRepo 229 .flatMapLatest { it.dataConnectionState } 230 .logDiffsForTable( 231 tableLogBuffer, 232 initialValue = activeRepo.value.dataConnectionState.value, 233 ) 234 .stateIn( 235 scope, 236 SharingStarted.WhileSubscribed(), 237 activeRepo.value.dataConnectionState.value, 238 ) 239 240 override val dataActivityDirection = 241 activeRepo 242 .flatMapLatest { it.dataActivityDirection } 243 .logDiffsForTable( 244 tableLogBuffer, 245 initialValue = activeRepo.value.dataActivityDirection.value, 246 ) 247 .stateIn( 248 scope, 249 SharingStarted.WhileSubscribed(), 250 activeRepo.value.dataActivityDirection.value, 251 ) 252 253 override val carrierNetworkChangeActive = 254 activeRepo 255 .flatMapLatest { it.carrierNetworkChangeActive } 256 .logDiffsForTable( 257 tableLogBuffer, 258 columnName = COL_CARRIER_NETWORK_CHANGE, 259 initialValue = activeRepo.value.carrierNetworkChangeActive.value, 260 ) 261 .stateIn( 262 scope, 263 SharingStarted.WhileSubscribed(), 264 activeRepo.value.carrierNetworkChangeActive.value, 265 ) 266 267 override val resolvedNetworkType = 268 activeRepo 269 .flatMapLatest { it.resolvedNetworkType } 270 .logDiffsForTable( 271 tableLogBuffer, 272 initialValue = activeRepo.value.resolvedNetworkType.value, 273 ) 274 .stateIn( 275 scope, 276 SharingStarted.WhileSubscribed(), 277 activeRepo.value.resolvedNetworkType.value, 278 ) 279 280 override val dataEnabled = 281 activeRepo 282 .flatMapLatest { it.dataEnabled } 283 .logDiffsForTable( 284 tableLogBuffer, 285 columnName = "dataEnabled", 286 initialValue = activeRepo.value.dataEnabled.value, 287 ) 288 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value) 289 290 override val inflateSignalStrength = 291 activeRepo 292 .flatMapLatest { it.inflateSignalStrength } 293 .logDiffsForTable( 294 tableLogBuffer, 295 columnName = "inflate", 296 initialValue = activeRepo.value.inflateSignalStrength.value, 297 ) 298 .stateIn( 299 scope, 300 SharingStarted.WhileSubscribed(), 301 activeRepo.value.inflateSignalStrength.value, 302 ) 303 304 override val allowNetworkSliceIndicator = 305 activeRepo 306 .flatMapLatest { it.allowNetworkSliceIndicator } 307 .logDiffsForTable( 308 tableLogBuffer, 309 columnName = "allowSlice", 310 initialValue = activeRepo.value.allowNetworkSliceIndicator.value, 311 ) 312 .stateIn( 313 scope, 314 SharingStarted.WhileSubscribed(), 315 activeRepo.value.allowNetworkSliceIndicator.value, 316 ) 317 318 override val numberOfLevels = 319 activeRepo 320 .flatMapLatest { it.numberOfLevels } 321 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value) 322 323 override val networkName = 324 activeRepo 325 .flatMapLatest { it.networkName } 326 .logDiffsForTable( 327 tableLogBuffer, 328 columnPrefix = "intent", 329 initialValue = activeRepo.value.networkName.value, 330 ) 331 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) 332 333 override val carrierName = 334 activeRepo 335 .flatMapLatest { it.carrierName } 336 .logDiffsForTable( 337 tableLogBuffer, 338 columnPrefix = "sub", 339 initialValue = activeRepo.value.carrierName.value, 340 ) 341 .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierName.value) 342 343 override val isAllowedDuringAirplaneMode = 344 activeRepo 345 .flatMapLatest { it.isAllowedDuringAirplaneMode } 346 .stateIn( 347 scope, 348 SharingStarted.WhileSubscribed(), 349 activeRepo.value.isAllowedDuringAirplaneMode.value, 350 ) 351 352 override val hasPrioritizedNetworkCapabilities = 353 activeRepo 354 .flatMapLatest { it.hasPrioritizedNetworkCapabilities } 355 .stateIn( 356 scope, 357 SharingStarted.WhileSubscribed(), 358 activeRepo.value.hasPrioritizedNetworkCapabilities.value, 359 ) 360 361 override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode() 362 363 fun dump(pw: PrintWriter) { 364 val ipw = IndentingPrintWriter(pw, " ") 365 366 ipw.println("MobileConnectionRepository[$subId]") 367 ipw.increaseIndent() 368 369 ipw.println("carrierMerged=${_isCarrierMerged.value}") 370 371 ipw.print("Type (cellular or carrier merged): ") 372 when (activeRepo.value) { 373 is CarrierMergedConnectionRepository -> ipw.println("Carrier merged") 374 is MobileConnectionRepositoryImpl -> ipw.println("Cellular") 375 } 376 377 ipw.increaseIndent() 378 ipw.println("Provider: ${activeRepo.value}") 379 ipw.decreaseIndent() 380 381 ipw.decreaseIndent() 382 } 383 384 class Factory 385 @Inject 386 constructor( 387 @Background private val scope: CoroutineScope, 388 private val logFactory: TableLogBufferFactory, 389 private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory, 390 private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory, 391 ) { 392 fun build( 393 subId: Int, 394 startingIsCarrierMerged: Boolean, 395 subscriptionModel: Flow<SubscriptionModel?>, 396 defaultNetworkName: NetworkNameModel, 397 networkNameSeparator: String, 398 ): FullMobileConnectionRepository { 399 val mobileLogger = 400 logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE) 401 402 return FullMobileConnectionRepository( 403 subId, 404 startingIsCarrierMerged, 405 mobileLogger, 406 subscriptionModel, 407 defaultNetworkName, 408 networkNameSeparator, 409 scope, 410 mobileRepoFactory, 411 carrierMergedRepoFactory, 412 ) 413 } 414 415 companion object { 416 /** The buffer size to use for logging. */ 417 const val MOBILE_CONNECTION_BUFFER_SIZE = 100 418 419 /** Returns a log buffer name for a mobile connection with the given [subId]. */ 420 fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]" 421 } 422 } 423 424 companion object { 425 const val COL_CARRIER_ID = "carrierId" 426 const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive" 427 const val COL_CDMA_LEVEL = "cdmaLevel" 428 const val COL_EMERGENCY = "emergencyOnly" 429 const val COL_IS_NTN = "isNtn" 430 const val COL_IS_GSM = "isGsm" 431 const val COL_IS_IN_SERVICE = "isInService" 432 const val COL_OPERATOR = "operatorName" 433 const val COL_PRIMARY_LEVEL = "primaryLevel" 434 const val COL_SATELLITE_LEVEL = "satelliteLevel" 435 const val COL_ROAMING = "roaming" 436 } 437 } 438