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