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.domain.interactor
18 
19 import android.platform.test.annotations.EnableFlags
20 import android.telephony.CellSignalStrength
21 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
22 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
23 import androidx.test.filters.SmallTest
24 import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
25 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
26 import com.android.settingslib.mobile.TelephonyIcons
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
30 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
31 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
32 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
33 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
34 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
35 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
36 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
37 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
38 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
39 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
40 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
41 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
42 import com.android.systemui.util.mockito.any
43 import com.android.systemui.util.mockito.mock
44 import com.android.systemui.util.mockito.whenever
45 import com.google.common.truth.Truth.assertThat
46 import kotlinx.coroutines.ExperimentalCoroutinesApi
47 import kotlinx.coroutines.flow.MutableStateFlow
48 import kotlinx.coroutines.flow.launchIn
49 import kotlinx.coroutines.flow.onEach
50 import kotlinx.coroutines.test.TestScope
51 import kotlinx.coroutines.test.UnconfinedTestDispatcher
52 import kotlinx.coroutines.test.runTest
53 import org.junit.Before
54 import org.junit.Test
55 import org.mockito.ArgumentMatchers.anyInt
56 import org.mockito.ArgumentMatchers.anyString
57 
58 @OptIn(ExperimentalCoroutinesApi::class)
59 @SmallTest
60 class MobileIconInteractorTest : SysuiTestCase() {
61     private lateinit var underTest: MobileIconInteractor
62     private val mobileMappingsProxy = FakeMobileMappingsProxy()
63     private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
64 
65     private val subscriptionModel =
66         MutableStateFlow(
67             SubscriptionModel(
68                 subscriptionId = SUB_1_ID,
69                 carrierName = DEFAULT_NAME,
70                 profileClass = PROFILE_CLASS_UNSET,
71             )
72         )
73 
74     private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
75 
76     private val testDispatcher = UnconfinedTestDispatcher()
77     private val testScope = TestScope(testDispatcher)
78 
79     @Before
setUpnull80     fun setUp() {
81         underTest = createInteractor()
82 
83         mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
84         connectionRepository.isInService.value = true
85     }
86 
87     @Test
gsm_usesGsmLevelnull88     fun gsm_usesGsmLevel() =
89         testScope.runTest {
90             connectionRepository.isGsm.value = true
91             connectionRepository.primaryLevel.value = GSM_LEVEL
92             connectionRepository.cdmaLevel.value = CDMA_LEVEL
93 
94             var latest: Int? = null
95             val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
96 
97             assertThat(latest).isEqualTo(GSM_LEVEL)
98 
99             job.cancel()
100         }
101 
102     @Test
gsm_alwaysShowCdmaTrue_stillUsesGsmLevelnull103     fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
104         testScope.runTest {
105             connectionRepository.isGsm.value = true
106             connectionRepository.primaryLevel.value = GSM_LEVEL
107             connectionRepository.cdmaLevel.value = CDMA_LEVEL
108             mobileIconsInteractor.alwaysUseCdmaLevel.value = true
109 
110             var latest: Int? = null
111             val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
112 
113             assertThat(latest).isEqualTo(GSM_LEVEL)
114 
115             job.cancel()
116         }
117 
118     @Test
notGsm_level_default_unknownnull119     fun notGsm_level_default_unknown() =
120         testScope.runTest {
121             connectionRepository.isGsm.value = false
122 
123             var latest: Int? = null
124             val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
125 
126             assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
127             job.cancel()
128         }
129 
130     @Test
notGsm_alwaysShowCdmaTrue_usesCdmaLevelnull131     fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
132         testScope.runTest {
133             connectionRepository.isGsm.value = false
134             connectionRepository.primaryLevel.value = GSM_LEVEL
135             connectionRepository.cdmaLevel.value = CDMA_LEVEL
136             mobileIconsInteractor.alwaysUseCdmaLevel.value = true
137 
138             var latest: Int? = null
139             val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
140 
141             assertThat(latest).isEqualTo(CDMA_LEVEL)
142 
143             job.cancel()
144         }
145 
146     @Test
notGsm_alwaysShowCdmaFalse_usesPrimaryLevelnull147     fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
148         testScope.runTest {
149             connectionRepository.isGsm.value = false
150             connectionRepository.primaryLevel.value = GSM_LEVEL
151             connectionRepository.cdmaLevel.value = CDMA_LEVEL
152             mobileIconsInteractor.alwaysUseCdmaLevel.value = false
153 
154             var latest: Int? = null
155             val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
156 
157             assertThat(latest).isEqualTo(GSM_LEVEL)
158 
159             job.cancel()
160         }
161 
162     @Test
numberOfLevels_comesFromRepo_whenApplicablenull163     fun numberOfLevels_comesFromRepo_whenApplicable() =
164         testScope.runTest {
165             var latest: Int? = null
166             val job =
167                 underTest.signalLevelIcon
168                     .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
169                     .launchIn(this)
170 
171             connectionRepository.numberOfLevels.value = 5
172             assertThat(latest).isEqualTo(5)
173 
174             connectionRepository.numberOfLevels.value = 4
175             assertThat(latest).isEqualTo(4)
176 
177             job.cancel()
178         }
179 
180     @Test
inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevelnull181     fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() =
182         testScope.runTest {
183             connectionRepository.inflateSignalStrength.value = false
184             val latest by collectLastValue(underTest.signalLevelIcon)
185 
186             connectionRepository.primaryLevel.value = 4
187             assertThat(latest!!.level).isEqualTo(4)
188 
189             connectionRepository.inflateSignalStrength.value = true
190             connectionRepository.primaryLevel.value = 4
191 
192             // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
193             assertThat(latest!!.level).isEqualTo(5)
194         }
195 
196     @Test
networkSlice_configOn_hasPrioritizedCaps_showsSlicenull197     fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() =
198         testScope.runTest {
199             connectionRepository.allowNetworkSliceIndicator.value = true
200             val latest by collectLastValue(underTest.showSliceAttribution)
201 
202             connectionRepository.hasPrioritizedNetworkCapabilities.value = true
203 
204             assertThat(latest).isTrue()
205         }
206 
207     @Test
networkSlice_configOn_noPrioritizedCaps_noSlicenull208     fun networkSlice_configOn_noPrioritizedCaps_noSlice() =
209         testScope.runTest {
210             connectionRepository.allowNetworkSliceIndicator.value = true
211             val latest by collectLastValue(underTest.showSliceAttribution)
212 
213             connectionRepository.hasPrioritizedNetworkCapabilities.value = false
214 
215             assertThat(latest).isFalse()
216         }
217 
218     @Test
networkSlice_configOff_hasPrioritizedCaps_noSlicenull219     fun networkSlice_configOff_hasPrioritizedCaps_noSlice() =
220         testScope.runTest {
221             connectionRepository.allowNetworkSliceIndicator.value = false
222             val latest by collectLastValue(underTest.showSliceAttribution)
223 
224             connectionRepository.hasPrioritizedNetworkCapabilities.value = true
225 
226             assertThat(latest).isFalse()
227         }
228 
229     @Test
networkSlice_configOff_noPrioritizedCaps_noSlicenull230     fun networkSlice_configOff_noPrioritizedCaps_noSlice() =
231         testScope.runTest {
232             connectionRepository.allowNetworkSliceIndicator.value = false
233             val latest by collectLastValue(underTest.showSliceAttribution)
234 
235             connectionRepository.hasPrioritizedNetworkCapabilities.value = false
236 
237             assertThat(latest).isFalse()
238         }
239 
240     @Test
iconGroup_three_gnull241     fun iconGroup_three_g() =
242         testScope.runTest {
243             connectionRepository.resolvedNetworkType.value =
244                 DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
245 
246             var latest: NetworkTypeIconModel? = null
247             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
248 
249             assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
250 
251             job.cancel()
252         }
253 
254     @Test
iconGroup_updates_on_changenull255     fun iconGroup_updates_on_change() =
256         testScope.runTest {
257             connectionRepository.resolvedNetworkType.value =
258                 DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
259 
260             var latest: NetworkTypeIconModel? = null
261             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
262 
263             connectionRepository.resolvedNetworkType.value =
264                 DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
265 
266             assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
267 
268             job.cancel()
269         }
270 
271     @Test
iconGroup_5g_override_typenull272     fun iconGroup_5g_override_type() =
273         testScope.runTest {
274             connectionRepository.resolvedNetworkType.value =
275                 OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
276 
277             var latest: NetworkTypeIconModel? = null
278             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
279 
280             assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
281 
282             job.cancel()
283         }
284 
285     @Test
iconGroup_default_if_no_lookupnull286     fun iconGroup_default_if_no_lookup() =
287         testScope.runTest {
288             connectionRepository.resolvedNetworkType.value =
289                 DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
290 
291             var latest: NetworkTypeIconModel? = null
292             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
293 
294             assertThat(latest)
295                 .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
296 
297             job.cancel()
298         }
299 
300     @Test
iconGroup_carrierMerged_usesOverridenull301     fun iconGroup_carrierMerged_usesOverride() =
302         testScope.runTest {
303             connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
304 
305             var latest: NetworkTypeIconModel? = null
306             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
307 
308             assertThat(latest)
309                 .isEqualTo(
310                     NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride)
311                 )
312 
313             job.cancel()
314         }
315 
316     @Test
overrideIcon_usesCarrierIdOverridenull317     fun overrideIcon_usesCarrierIdOverride() =
318         testScope.runTest {
319             val overrides =
320                 mock<MobileIconCarrierIdOverrides>().also {
321                     whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true)
322                     whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234)
323                 }
324 
325             underTest = createInteractor(overrides)
326 
327             connectionRepository.resolvedNetworkType.value =
328                 DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
329 
330             var latest: NetworkTypeIconModel? = null
331             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
332 
333             assertThat(latest)
334                 .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
335 
336             job.cancel()
337         }
338 
339     @Test
alwaysShowDataRatIcon_matchesParentnull340     fun alwaysShowDataRatIcon_matchesParent() =
341         testScope.runTest {
342             var latest: Boolean? = null
343             val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
344 
345             mobileIconsInteractor.alwaysShowDataRatIcon.value = true
346             assertThat(latest).isTrue()
347 
348             mobileIconsInteractor.alwaysShowDataRatIcon.value = false
349             assertThat(latest).isFalse()
350 
351             job.cancel()
352         }
353 
354     @Test
dataState_connectednull355     fun dataState_connected() =
356         testScope.runTest {
357             var latest: Boolean? = null
358             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
359 
360             connectionRepository.dataConnectionState.value = DataConnectionState.Connected
361 
362             assertThat(latest).isTrue()
363 
364             job.cancel()
365         }
366 
367     @Test
dataState_notConnectednull368     fun dataState_notConnected() =
369         testScope.runTest {
370             var latest: Boolean? = null
371             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
372 
373             connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
374 
375             assertThat(latest).isFalse()
376 
377             job.cancel()
378         }
379 
380     @Test
isInService_usesRepositoryValuenull381     fun isInService_usesRepositoryValue() =
382         testScope.runTest {
383             var latest: Boolean? = null
384             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
385 
386             connectionRepository.isInService.value = true
387 
388             assertThat(latest).isTrue()
389 
390             connectionRepository.isInService.value = false
391 
392             assertThat(latest).isFalse()
393 
394             job.cancel()
395         }
396 
397     @Test
roaming_isGsm_usesConnectionModelnull398     fun roaming_isGsm_usesConnectionModel() =
399         testScope.runTest {
400             var latest: Boolean? = null
401             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
402 
403             connectionRepository.cdmaRoaming.value = true
404             connectionRepository.isGsm.value = true
405             connectionRepository.isRoaming.value = false
406 
407             assertThat(latest).isFalse()
408 
409             connectionRepository.isRoaming.value = true
410 
411             assertThat(latest).isTrue()
412 
413             job.cancel()
414         }
415 
416     @Test
roaming_isCdma_usesCdmaRoamingBitnull417     fun roaming_isCdma_usesCdmaRoamingBit() =
418         testScope.runTest {
419             var latest: Boolean? = null
420             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
421 
422             connectionRepository.cdmaRoaming.value = false
423             connectionRepository.isGsm.value = false
424             connectionRepository.isRoaming.value = true
425 
426             assertThat(latest).isFalse()
427 
428             connectionRepository.cdmaRoaming.value = true
429             connectionRepository.isGsm.value = false
430             connectionRepository.isRoaming.value = false
431 
432             assertThat(latest).isTrue()
433 
434             job.cancel()
435         }
436 
437     @Test
roaming_falseWhileCarrierNetworkChangeActivenull438     fun roaming_falseWhileCarrierNetworkChangeActive() =
439         testScope.runTest {
440             var latest: Boolean? = null
441             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
442 
443             connectionRepository.cdmaRoaming.value = true
444             connectionRepository.isGsm.value = false
445             connectionRepository.isRoaming.value = true
446             connectionRepository.carrierNetworkChangeActive.value = true
447 
448             assertThat(latest).isFalse()
449 
450             connectionRepository.cdmaRoaming.value = true
451             connectionRepository.isGsm.value = true
452 
453             assertThat(latest).isFalse()
454 
455             job.cancel()
456         }
457 
458     @Test
networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefaultnull459     fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
460         testScope.runTest {
461             var latest: NetworkNameModel? = null
462             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
463 
464             val testOperatorName = "operatorAlphaShort"
465 
466             // Default network name, operator name is non-null, uses the operator name
467             connectionRepository.networkName.value = DEFAULT_NAME_MODEL
468             connectionRepository.operatorAlphaShort.value = testOperatorName
469 
470             assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
471 
472             // Default network name, operator name is null, uses the default
473             connectionRepository.operatorAlphaShort.value = null
474 
475             assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
476 
477             // Derived network name, operator name non-null, uses the derived name
478             connectionRepository.networkName.value = DERIVED_NAME_MODEL
479             connectionRepository.operatorAlphaShort.value = testOperatorName
480 
481             assertThat(latest).isEqualTo(DERIVED_NAME_MODEL)
482 
483             job.cancel()
484         }
485 
486     @Test
networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefaultnull487     fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
488         testScope.runTest {
489             var latest: String? = null
490             val job = underTest.carrierName.onEach { latest = it }.launchIn(this)
491 
492             val testOperatorName = "operatorAlphaShort"
493 
494             // Default network name, operator name is non-null, uses the operator name
495             connectionRepository.carrierName.value = DEFAULT_NAME_MODEL
496             connectionRepository.operatorAlphaShort.value = testOperatorName
497 
498             assertThat(latest).isEqualTo(testOperatorName)
499 
500             // Default network name, operator name is null, uses the default
501             connectionRepository.operatorAlphaShort.value = null
502 
503             assertThat(latest).isEqualTo(DEFAULT_NAME)
504 
505             // Derived network name, operator name non-null, uses the derived name
506             connectionRepository.carrierName.value =
507                 NetworkNameModel.SubscriptionDerived(DERIVED_NAME)
508             connectionRepository.operatorAlphaShort.value = testOperatorName
509 
510             assertThat(latest).isEqualTo(DERIVED_NAME)
511 
512             job.cancel()
513         }
514 
515     @Test
isSingleCarrier_matchesParentnull516     fun isSingleCarrier_matchesParent() =
517         testScope.runTest {
518             var latest: Boolean? = null
519             val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
520 
521             mobileIconsInteractor.isSingleCarrier.value = true
522             assertThat(latest).isTrue()
523 
524             mobileIconsInteractor.isSingleCarrier.value = false
525             assertThat(latest).isFalse()
526 
527             job.cancel()
528         }
529 
530     @Test
isForceHidden_matchesParentnull531     fun isForceHidden_matchesParent() =
532         testScope.runTest {
533             var latest: Boolean? = null
534             val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
535 
536             mobileIconsInteractor.isForceHidden.value = true
537             assertThat(latest).isTrue()
538 
539             mobileIconsInteractor.isForceHidden.value = false
540             assertThat(latest).isFalse()
541 
542             job.cancel()
543         }
544 
545     @Test
isAllowedDuringAirplaneMode_matchesReponull546     fun isAllowedDuringAirplaneMode_matchesRepo() =
547         testScope.runTest {
548             val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
549 
550             connectionRepository.isAllowedDuringAirplaneMode.value = true
551             assertThat(latest).isTrue()
552 
553             connectionRepository.isAllowedDuringAirplaneMode.value = false
554             assertThat(latest).isFalse()
555         }
556 
557     @Test
cellBasedIconId_correctLevel_notCutoutnull558     fun cellBasedIconId_correctLevel_notCutout() =
559         testScope.runTest {
560             connectionRepository.isNonTerrestrial.value = false
561             connectionRepository.isInService.value = true
562             connectionRepository.primaryLevel.value = 1
563             connectionRepository.setDataEnabled(false)
564             connectionRepository.isNonTerrestrial.value = false
565 
566             var latest: SignalIconModel.Cellular? = null
567             val job =
568                 underTest.signalLevelIcon
569                     .onEach { latest = it as? SignalIconModel.Cellular }
570                     .launchIn(this)
571 
572             assertThat(latest?.level).isEqualTo(1)
573             assertThat(latest?.showExclamationMark).isFalse()
574 
575             job.cancel()
576         }
577 
578     @Test
icon_usesLevelFromInteractornull579     fun icon_usesLevelFromInteractor() =
580         testScope.runTest {
581             connectionRepository.isNonTerrestrial.value = false
582             connectionRepository.isInService.value = true
583 
584             var latest: SignalIconModel? = null
585             val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
586 
587             connectionRepository.primaryLevel.value = 3
588             assertThat(latest!!.level).isEqualTo(3)
589 
590             connectionRepository.primaryLevel.value = 1
591             assertThat(latest!!.level).isEqualTo(1)
592 
593             job.cancel()
594         }
595 
596     @Test
cellBasedIcon_usesNumberOfLevelsFromInteractornull597     fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
598         testScope.runTest {
599             connectionRepository.isNonTerrestrial.value = false
600 
601             var latest: SignalIconModel.Cellular? = null
602             val job =
603                 underTest.signalLevelIcon
604                     .onEach { latest = it as? SignalIconModel.Cellular }
605                     .launchIn(this)
606 
607             connectionRepository.numberOfLevels.value = 5
608             assertThat(latest!!.numberOfLevels).isEqualTo(5)
609 
610             connectionRepository.numberOfLevels.value = 2
611             assertThat(latest!!.numberOfLevels).isEqualTo(2)
612 
613             job.cancel()
614         }
615 
616     @Test
cellBasedIcon_defaultDataDisabled_showExclamationTruenull617     fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
618         testScope.runTest {
619             connectionRepository.isNonTerrestrial.value = false
620             mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
621 
622             var latest: SignalIconModel.Cellular? = null
623             val job =
624                 underTest.signalLevelIcon
625                     .onEach { latest = it as? SignalIconModel.Cellular }
626                     .launchIn(this)
627 
628             assertThat(latest!!.showExclamationMark).isTrue()
629 
630             job.cancel()
631         }
632 
633     @Test
cellBasedIcon_defaultConnectionFailed_showExclamationTruenull634     fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
635         testScope.runTest {
636             connectionRepository.isNonTerrestrial.value = false
637             mobileIconsInteractor.isDefaultConnectionFailed.value = true
638 
639             var latest: SignalIconModel.Cellular? = null
640             val job =
641                 underTest.signalLevelIcon
642                     .onEach { latest = it as? SignalIconModel.Cellular }
643                     .launchIn(this)
644 
645             assertThat(latest!!.showExclamationMark).isTrue()
646 
647             job.cancel()
648         }
649 
650     @Test
cellBasedIcon_enabledAndNotFailed_showExclamationFalsenull651     fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
652         testScope.runTest {
653             connectionRepository.isNonTerrestrial.value = false
654             connectionRepository.isInService.value = true
655             mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
656             mobileIconsInteractor.isDefaultConnectionFailed.value = false
657 
658             var latest: SignalIconModel.Cellular? = null
659             val job =
660                 underTest.signalLevelIcon
661                     .onEach { latest = it as? SignalIconModel.Cellular }
662                     .launchIn(this)
663 
664             assertThat(latest!!.showExclamationMark).isFalse()
665 
666             job.cancel()
667         }
668 
669     @Test
cellBasedIcon_usesEmptyState_whenNotInServicenull670     fun cellBasedIcon_usesEmptyState_whenNotInService() =
671         testScope.runTest {
672             var latest: SignalIconModel.Cellular? = null
673             val job =
674                 underTest.signalLevelIcon
675                     .onEach { latest = it as? SignalIconModel.Cellular }
676                     .launchIn(this)
677 
678             connectionRepository.isNonTerrestrial.value = false
679             connectionRepository.isInService.value = false
680 
681             assertThat(latest?.level).isEqualTo(0)
682             assertThat(latest?.showExclamationMark).isTrue()
683 
684             // Changing the level doesn't overwrite the disabled state
685             connectionRepository.primaryLevel.value = 2
686             assertThat(latest?.level).isEqualTo(0)
687             assertThat(latest?.showExclamationMark).isTrue()
688 
689             // Once back in service, the regular icon appears
690             connectionRepository.isInService.value = true
691             assertThat(latest?.level).isEqualTo(2)
692             assertThat(latest?.showExclamationMark).isFalse()
693 
694             job.cancel()
695         }
696 
697     @Test
cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeModenull698     fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
699         testScope.runTest {
700             var latest: SignalIconModel.Cellular? = null
701             val job =
702                 underTest.signalLevelIcon
703                     .onEach { latest = it as? SignalIconModel.Cellular? }
704                     .launchIn(this)
705 
706             connectionRepository.isNonTerrestrial.value = false
707             connectionRepository.isInService.value = true
708             connectionRepository.carrierNetworkChangeActive.value = true
709             connectionRepository.primaryLevel.value = 1
710             connectionRepository.cdmaLevel.value = 1
711 
712             assertThat(latest!!.level).isEqualTo(1)
713             assertThat(latest!!.carrierNetworkChange).isTrue()
714 
715             // SignalIconModel respects the current level
716             connectionRepository.primaryLevel.value = 2
717 
718             assertThat(latest!!.level).isEqualTo(2)
719             assertThat(latest!!.carrierNetworkChange).isTrue()
720 
721             job.cancel()
722         }
723 
724     @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
725     @Test
satBasedIcon_isUsedWhenNonTerrestrialnull726     fun satBasedIcon_isUsedWhenNonTerrestrial() =
727         testScope.runTest {
728             val latest by collectLastValue(underTest.signalLevelIcon)
729 
730             // Start off using cellular
731             assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
732 
733             connectionRepository.isNonTerrestrial.value = true
734 
735             assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
736         }
737 
createInteractornull738     private fun createInteractor(
739         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
740     ) =
741         MobileIconInteractorImpl(
742             testScope.backgroundScope,
743             mobileIconsInteractor.activeDataConnectionHasDataEnabled,
744             mobileIconsInteractor.alwaysShowDataRatIcon,
745             mobileIconsInteractor.alwaysUseCdmaLevel,
746             mobileIconsInteractor.isSingleCarrier,
747             mobileIconsInteractor.mobileIsDefault,
748             mobileIconsInteractor.defaultMobileIconMapping,
749             mobileIconsInteractor.defaultMobileIconGroup,
750             mobileIconsInteractor.isDefaultConnectionFailed,
751             mobileIconsInteractor.isForceHidden,
752             connectionRepository,
753             context,
754             overrides,
755         )
756 
757     companion object {
758         private const val GSM_LEVEL = 1
759         private const val CDMA_LEVEL = 2
760 
761         private const val SUB_1_ID = 1
762 
763         private const val DEFAULT_NAME = "test default name"
764         private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
765         private const val DERIVED_NAME = "test derived name"
766         private val DERIVED_NAME_MODEL = NetworkNameModel.IntentDerived(DERIVED_NAME)
767     }
768 }
769