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