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