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