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