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.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN 20 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID 21 import android.telephony.TelephonyManager 22 import android.util.Log 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Background 25 import com.android.systemui.log.table.TableLogBuffer 26 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState 27 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 28 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType 29 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 30 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS 31 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository 32 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel 33 import javax.inject.Inject 34 import kotlin.coroutines.CoroutineContext 35 import kotlinx.coroutines.CoroutineScope 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.MutableStateFlow 38 import kotlinx.coroutines.flow.SharingStarted 39 import kotlinx.coroutines.flow.StateFlow 40 import kotlinx.coroutines.flow.asStateFlow 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.map 43 import kotlinx.coroutines.flow.stateIn 44 import kotlinx.coroutines.withContext 45 46 /** 47 * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is 48 * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually 49 * displayed as a mobile network triangle. 50 * 51 * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information. 52 * 53 * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile 54 * connection. 55 */ 56 class CarrierMergedConnectionRepository( 57 override val subId: Int, 58 override val tableLogBuffer: TableLogBuffer, 59 private val telephonyManager: TelephonyManager, 60 private val bgContext: CoroutineContext, 61 @Background private val scope: CoroutineScope, 62 val wifiRepository: WifiRepository, 63 ) : MobileConnectionRepository { 64 init { 65 if (telephonyManager.subscriptionId != subId) { 66 throw IllegalStateException( 67 "CarrierMergedRepo: TelephonyManager should be created with subId($subId). " + 68 "Found ${telephonyManager.subscriptionId} instead." 69 ) 70 } 71 } 72 73 /** 74 * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged 75 * network. 76 */ 77 private val network: Flow<WifiNetworkModel.CarrierMerged?> = 78 combine( 79 wifiRepository.isWifiEnabled, 80 wifiRepository.isWifiDefault, 81 wifiRepository.wifiNetwork, 82 ) { isEnabled, isDefault, network -> 83 when { 84 !isEnabled -> null 85 !isDefault -> null 86 network !is WifiNetworkModel.CarrierMerged -> null 87 network.subscriptionId != subId -> { 88 Log.w( 89 TAG, 90 "Connection repo subId=$subId " + 91 "does not equal wifi repo subId=${network.subscriptionId}; " + 92 "not showing carrier merged", 93 ) 94 null 95 } 96 else -> network 97 } 98 } 99 100 override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow() 101 102 override val networkName: StateFlow<NetworkNameModel> = 103 network 104 // The SIM operator name should be the same throughout the lifetime of a subId, **but** 105 // it may not be available when this repo is created because it takes time to load. To 106 // be safe, we re-fetch it each time the network has changed. 107 .map { NetworkNameModel.SimDerived(telephonyManager.simOperatorName) } 108 .stateIn( 109 scope, 110 SharingStarted.WhileSubscribed(), 111 NetworkNameModel.SimDerived(telephonyManager.simOperatorName), 112 ) 113 114 override val carrierName: StateFlow<NetworkNameModel> = networkName 115 116 override val numberOfLevels: StateFlow<Int> = 117 wifiRepository.wifiNetwork 118 .map { 119 if (it is WifiNetworkModel.CarrierMerged) { 120 it.numberOfLevels 121 } else { 122 DEFAULT_NUM_LEVELS 123 } 124 } 125 .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS) 126 127 override val primaryLevel = 128 network 129 .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN } 130 .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN) 131 132 override val cdmaLevel = 133 network 134 .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN } 135 .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN) 136 137 override val dataActivityDirection = wifiRepository.wifiActivity 138 139 override val resolvedNetworkType = 140 network 141 .map { 142 if (it != null) { 143 ResolvedNetworkType.CarrierMergedNetworkType 144 } else { 145 ResolvedNetworkType.UnknownNetworkType 146 } 147 } 148 .stateIn( 149 scope, 150 SharingStarted.WhileSubscribed(), 151 ResolvedNetworkType.UnknownNetworkType, 152 ) 153 154 override val dataConnectionState = 155 network 156 .map { 157 if (it != null) { 158 DataConnectionState.Connected 159 } else { 160 DataConnectionState.Disconnected 161 } 162 } 163 .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected) 164 165 override val isRoaming = MutableStateFlow(false).asStateFlow() 166 override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow() 167 override val inflateSignalStrength = MutableStateFlow(false).asStateFlow() 168 override val allowNetworkSliceIndicator = MutableStateFlow(false).asStateFlow() 169 override val isEmergencyOnly = MutableStateFlow(false).asStateFlow() 170 override val operatorAlphaShort = MutableStateFlow(null).asStateFlow() 171 override val isInService = MutableStateFlow(true).asStateFlow() 172 override val isNonTerrestrial = MutableStateFlow(false).asStateFlow() 173 override val isGsm = MutableStateFlow(false).asStateFlow() 174 override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow() 175 override val satelliteLevel = MutableStateFlow(0) 176 177 /** 178 * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because 179 * they occur over wifi, it's possible to have a valid carrier merged connection even during 180 * airplane mode. See b/291993542. 181 */ 182 override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow() 183 184 /** 185 * It's not currently considered possible that a carrier merged network can have these 186 * prioritized capabilities. If we need to track them, we can add the same check as is in 187 * [MobileConnectionRepositoryImpl]. 188 */ 189 override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow() 190 191 override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled 192 193 override suspend fun isInEcmMode(): Boolean = 194 withContext(bgContext) { telephonyManager.emergencyCallbackMode } 195 196 companion object { 197 // Carrier merged is never roaming 198 private const val ROAMING = false 199 } 200 201 @SysUISingleton 202 class Factory 203 @Inject 204 constructor( 205 private val telephonyManager: TelephonyManager, 206 @Background private val bgContext: CoroutineContext, 207 @Background private val scope: CoroutineScope, 208 private val wifiRepository: WifiRepository, 209 ) { 210 fun build(subId: Int, mobileLogger: TableLogBuffer): MobileConnectionRepository { 211 return CarrierMergedConnectionRepository( 212 subId, 213 mobileLogger, 214 telephonyManager.createForSubscriptionId(subId), 215 bgContext, 216 scope, 217 wifiRepository, 218 ) 219 } 220 } 221 } 222 223 private const val TAG = "CarrierMergedConnectionRepository" 224