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 androidx.annotation.VisibleForTesting
20 import com.android.systemui.dagger.qualifiers.Application
21 import com.android.systemui.log.table.TableLogBuffer
22 import com.android.systemui.log.table.TableLogBufferFactory
23 import com.android.systemui.log.table.logDiffsForTable
24 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
25 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
26 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
27 import javax.inject.Inject
28 import kotlinx.coroutines.CoroutineScope
29 import kotlinx.coroutines.ExperimentalCoroutinesApi
30 import kotlinx.coroutines.flow.MutableStateFlow
31 import kotlinx.coroutines.flow.SharingStarted
32 import kotlinx.coroutines.flow.StateFlow
33 import kotlinx.coroutines.flow.flatMapLatest
34 import kotlinx.coroutines.flow.mapLatest
35 import kotlinx.coroutines.flow.stateIn
36 
37 /**
38  * A repository that fully implements a mobile connection.
39  *
40  * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
41  * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
42  * switches between the two types of connections based on whether the connection is currently
43  * carrier merged (see [setIsCarrierMerged]).
44  */
45 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
46 @OptIn(ExperimentalCoroutinesApi::class)
47 class FullMobileConnectionRepository(
48     override val subId: Int,
49     startingIsCarrierMerged: Boolean,
50     override val tableLogBuffer: TableLogBuffer,
51     subscriptionModel: StateFlow<SubscriptionModel?>,
52     private val defaultNetworkName: NetworkNameModel,
53     private val networkNameSeparator: String,
54     @Application scope: CoroutineScope,
55     private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
56     private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
57 ) : MobileConnectionRepository {
58     /**
59      * Sets whether this connection is a typical mobile connection or a carrier merged connection.
60      */
61     fun setIsCarrierMerged(isCarrierMerged: Boolean) {
62         _isCarrierMerged.value = isCarrierMerged
63     }
64 
65     /**
66      * Returns true if this repo is currently for a carrier merged connection and false otherwise.
67      */
68     @VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value
69 
70     private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged)
71     private val isCarrierMerged: StateFlow<Boolean> =
72         _isCarrierMerged
73             .logDiffsForTable(
74                 tableLogBuffer,
75                 columnPrefix = "",
76                 columnName = "isCarrierMerged",
77                 initialValue = startingIsCarrierMerged,
78             )
79             .stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged)
80 
81     private val mobileRepo: MobileConnectionRepository by lazy {
82         mobileRepoFactory.build(
83             subId,
84             tableLogBuffer,
85             subscriptionModel,
86             defaultNetworkName,
87             networkNameSeparator,
88         )
89     }
90 
91     private val carrierMergedRepo: MobileConnectionRepository by lazy {
92         carrierMergedRepoFactory.build(subId, tableLogBuffer)
93     }
94 
95     @VisibleForTesting
96     internal val activeRepo: StateFlow<MobileConnectionRepository> = run {
97         val initial =
98             if (startingIsCarrierMerged) {
99                 carrierMergedRepo
100             } else {
101                 mobileRepo
102             }
103 
104         this.isCarrierMerged
105             .mapLatest { isCarrierMerged ->
106                 if (isCarrierMerged) {
107                     carrierMergedRepo
108                 } else {
109                     mobileRepo
110                 }
111             }
112             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
113     }
114 
115     override val carrierId =
116         activeRepo
117             .flatMapLatest { it.carrierId }
118             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierId.value)
119 
120     override val cdmaRoaming =
121         activeRepo
122             .flatMapLatest { it.cdmaRoaming }
123             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
124 
125     override val isEmergencyOnly =
126         activeRepo
127             .flatMapLatest { it.isEmergencyOnly }
128             .logDiffsForTable(
129                 tableLogBuffer,
130                 columnPrefix = "",
131                 columnName = COL_EMERGENCY,
132                 activeRepo.value.isEmergencyOnly.value
133             )
134             .stateIn(
135                 scope,
136                 SharingStarted.WhileSubscribed(),
137                 activeRepo.value.isEmergencyOnly.value
138             )
139 
140     override val isRoaming =
141         activeRepo
142             .flatMapLatest { it.isRoaming }
143             .logDiffsForTable(
144                 tableLogBuffer,
145                 columnPrefix = "",
146                 columnName = COL_ROAMING,
147                 activeRepo.value.isRoaming.value
148             )
149             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
150 
151     override val operatorAlphaShort =
152         activeRepo
153             .flatMapLatest { it.operatorAlphaShort }
154             .logDiffsForTable(
155                 tableLogBuffer,
156                 columnPrefix = "",
157                 columnName = COL_OPERATOR,
158                 activeRepo.value.operatorAlphaShort.value
159             )
160             .stateIn(
161                 scope,
162                 SharingStarted.WhileSubscribed(),
163                 activeRepo.value.operatorAlphaShort.value
164             )
165 
166     override val isInService =
167         activeRepo
168             .flatMapLatest { it.isInService }
169             .logDiffsForTable(
170                 tableLogBuffer,
171                 columnPrefix = "",
172                 columnName = COL_IS_IN_SERVICE,
173                 activeRepo.value.isInService.value
174             )
175             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
176 
177     override val isGsm =
178         activeRepo
179             .flatMapLatest { it.isGsm }
180             .logDiffsForTable(
181                 tableLogBuffer,
182                 columnPrefix = "",
183                 columnName = COL_IS_GSM,
184                 activeRepo.value.isGsm.value
185             )
186             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
187 
188     override val cdmaLevel =
189         activeRepo
190             .flatMapLatest { it.cdmaLevel }
191             .logDiffsForTable(
192                 tableLogBuffer,
193                 columnPrefix = "",
194                 columnName = COL_CDMA_LEVEL,
195                 activeRepo.value.cdmaLevel.value
196             )
197             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
198 
199     override val primaryLevel =
200         activeRepo
201             .flatMapLatest { it.primaryLevel }
202             .logDiffsForTable(
203                 tableLogBuffer,
204                 columnPrefix = "",
205                 columnName = COL_PRIMARY_LEVEL,
206                 activeRepo.value.primaryLevel.value
207             )
208             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
209 
210     override val dataConnectionState =
211         activeRepo
212             .flatMapLatest { it.dataConnectionState }
213             .logDiffsForTable(
214                 tableLogBuffer,
215                 columnPrefix = "",
216                 activeRepo.value.dataConnectionState.value
217             )
218             .stateIn(
219                 scope,
220                 SharingStarted.WhileSubscribed(),
221                 activeRepo.value.dataConnectionState.value
222             )
223 
224     override val dataActivityDirection =
225         activeRepo
226             .flatMapLatest { it.dataActivityDirection }
227             .logDiffsForTable(
228                 tableLogBuffer,
229                 columnPrefix = "",
230                 activeRepo.value.dataActivityDirection.value
231             )
232             .stateIn(
233                 scope,
234                 SharingStarted.WhileSubscribed(),
235                 activeRepo.value.dataActivityDirection.value
236             )
237 
238     override val carrierNetworkChangeActive =
239         activeRepo
240             .flatMapLatest { it.carrierNetworkChangeActive }
241             .logDiffsForTable(
242                 tableLogBuffer,
243                 columnPrefix = "",
244                 columnName = COL_CARRIER_NETWORK_CHANGE,
245                 activeRepo.value.carrierNetworkChangeActive.value
246             )
247             .stateIn(
248                 scope,
249                 SharingStarted.WhileSubscribed(),
250                 activeRepo.value.carrierNetworkChangeActive.value
251             )
252 
253     override val resolvedNetworkType =
254         activeRepo
255             .flatMapLatest { it.resolvedNetworkType }
256             .logDiffsForTable(
257                 tableLogBuffer,
258                 columnPrefix = "",
259                 activeRepo.value.resolvedNetworkType.value
260             )
261             .stateIn(
262                 scope,
263                 SharingStarted.WhileSubscribed(),
264                 activeRepo.value.resolvedNetworkType.value
265             )
266 
267     override val dataEnabled =
268         activeRepo
269             .flatMapLatest { it.dataEnabled }
270             .logDiffsForTable(
271                 tableLogBuffer,
272                 columnPrefix = "",
273                 columnName = "dataEnabled",
274                 initialValue = activeRepo.value.dataEnabled.value,
275             )
276             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
277 
278     override val numberOfLevels =
279         activeRepo
280             .flatMapLatest { it.numberOfLevels }
281             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value)
282 
283     override val networkName =
284         activeRepo
285             .flatMapLatest { it.networkName }
286             .logDiffsForTable(
287                 tableLogBuffer,
288                 columnPrefix = "",
289                 initialValue = activeRepo.value.networkName.value,
290             )
291             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
292 
293     override val carrierName =
294         activeRepo
295             .flatMapLatest { it.carrierName }
296             .logDiffsForTable(
297                 tableLogBuffer,
298                 columnPrefix = "",
299                 initialValue = activeRepo.value.carrierName.value,
300             )
301             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierName.value)
302 
303     override val isAllowedDuringAirplaneMode =
304         activeRepo
305             .flatMapLatest { it.isAllowedDuringAirplaneMode }
306             .stateIn(
307                 scope,
308                 SharingStarted.WhileSubscribed(),
309                 activeRepo.value.isAllowedDuringAirplaneMode.value,
310             )
311 
312     class Factory
313     @Inject
314     constructor(
315         @Application private val scope: CoroutineScope,
316         private val logFactory: TableLogBufferFactory,
317         private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
318         private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
319     ) {
320         fun build(
321             subId: Int,
322             startingIsCarrierMerged: Boolean,
323             subscriptionModel: StateFlow<SubscriptionModel?>,
324             defaultNetworkName: NetworkNameModel,
325             networkNameSeparator: String,
326         ): FullMobileConnectionRepository {
327             val mobileLogger =
328                 logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
329 
330             return FullMobileConnectionRepository(
331                 subId,
332                 startingIsCarrierMerged,
333                 mobileLogger,
334                 subscriptionModel,
335                 defaultNetworkName,
336                 networkNameSeparator,
337                 scope,
338                 mobileRepoFactory,
339                 carrierMergedRepoFactory,
340             )
341         }
342 
343         companion object {
344             /** The buffer size to use for logging. */
345             const val MOBILE_CONNECTION_BUFFER_SIZE = 100
346 
347             /** Returns a log buffer name for a mobile connection with the given [subId]. */
348             fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
349         }
350     }
351 
352     companion object {
353         const val COL_CARRIER_ID = "carrierId"
354         const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
355         const val COL_CDMA_LEVEL = "cdmaLevel"
356         const val COL_EMERGENCY = "emergencyOnly"
357         const val COL_IS_GSM = "isGsm"
358         const val COL_IS_IN_SERVICE = "isInService"
359         const val COL_OPERATOR = "operatorName"
360         const val COL_PRIMARY_LEVEL = "primaryLevel"
361         const val COL_ROAMING = "roaming"
362     }
363 }
364