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