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.annotation.SuppressLint
20 import android.content.Intent
21 import android.net.ConnectivityManager
22 import android.net.Network
23 import android.net.NetworkCapabilities
24 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
25 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
26 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
27 import android.net.NetworkCapabilities.TRANSPORT_WIFI
28 import android.net.vcn.VcnTransportInfo
29 import android.net.wifi.WifiInfo
30 import android.net.wifi.WifiManager
31 import android.os.ParcelUuid
32 import android.telephony.CarrierConfigManager
33 import android.telephony.ServiceState
34 import android.telephony.SubscriptionInfo
35 import android.telephony.SubscriptionManager
36 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
37 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
38 import android.telephony.TelephonyCallback
39 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
40 import android.telephony.TelephonyManager
41 import android.testing.TestableLooper
42 import androidx.test.filters.SmallTest
43 import com.android.internal.telephony.PhoneConstants
44 import com.android.keyguard.KeyguardUpdateMonitor
45 import com.android.keyguard.KeyguardUpdateMonitorCallback
46 import com.android.settingslib.R
47 import com.android.settingslib.mobile.MobileMappings
48 import com.android.systemui.SysuiTestCase
49 import com.android.systemui.coroutines.collectLastValue
50 import com.android.systemui.flags.FakeFeatureFlagsClassic
51 import com.android.systemui.flags.Flags
52 import com.android.systemui.log.LogBuffer
53 import com.android.systemui.log.table.TableLogBufferFactory
54 import com.android.systemui.log.table.logcatTableLogBuffer
55 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
56 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
57 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
58 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
59 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
60 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
61 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
62 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
63 import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
64 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
65 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
66 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
67 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
68 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
69 import com.android.systemui.testKosmos
70 import com.android.systemui.user.data.repository.fakeUserRepository
71 import com.android.systemui.user.data.repository.userRepository
72 import com.android.systemui.util.concurrency.FakeExecutor
73 import com.android.systemui.util.mockito.argumentCaptor
74 import com.android.systemui.util.mockito.capture
75 import com.android.systemui.util.mockito.eq
76 import com.android.systemui.util.time.FakeSystemClock
77 import com.android.wifitrackerlib.MergedCarrierEntry
78 import com.android.wifitrackerlib.WifiEntry
79 import com.android.wifitrackerlib.WifiPickerTracker
80 import com.google.common.truth.Truth.assertThat
81 import java.util.UUID
82 import kotlinx.coroutines.ExperimentalCoroutinesApi
83 import kotlinx.coroutines.flow.filterNotNull
84 import kotlinx.coroutines.flow.launchIn
85 import kotlinx.coroutines.flow.onEach
86 import kotlinx.coroutines.test.StandardTestDispatcher
87 import kotlinx.coroutines.test.TestScope
88 import kotlinx.coroutines.test.runCurrent
89 import kotlinx.coroutines.test.runTest
90 import org.junit.Assert.assertTrue
91 import org.junit.Before
92 import org.junit.Ignore
93 import org.junit.Test
94 import org.mockito.ArgumentMatchers.anyInt
95 import org.mockito.ArgumentMatchers.anyString
96 import org.mockito.Mock
97 import org.mockito.Mockito.verify
98 import org.mockito.MockitoAnnotations
99 import org.mockito.kotlin.any
100 import org.mockito.kotlin.mock
101 import org.mockito.kotlin.whenever
102 
103 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
104 @OptIn(ExperimentalCoroutinesApi::class)
105 @SmallTest
106 // This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
107 // to run the callback and this makes the looper place nicely with TestScope etc.
108 @TestableLooper.RunWithLooper
109 class MobileConnectionsRepositoryTest : SysuiTestCase() {
110     private val kosmos = testKosmos()
111 
112     private val flags =
113         FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
114 
115     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
116     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
117     private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
118     private lateinit var connectivityRepository: ConnectivityRepository
119     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
120     private lateinit var wifiRepository: WifiRepository
121     private lateinit var carrierConfigRepository: CarrierConfigRepository
122 
123     @Mock private lateinit var connectivityManager: ConnectivityManager
124     @Mock private lateinit var subscriptionManager: SubscriptionManager
125     @Mock private lateinit var telephonyManager: TelephonyManager
126     @Mock private lateinit var logger: MobileInputLogger
127     private val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
128     @Mock private lateinit var logBufferFactory: TableLogBufferFactory
129     @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
130     @Mock private lateinit var wifiManager: WifiManager
131     @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
132     @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
133     private val wifiTableLogBuffer = logcatTableLogBuffer(kosmos, "wifiTableLog")
134 
135     private val mobileMappings = FakeMobileMappingsProxy()
136     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
137     private val mainExecutor = FakeExecutor(FakeSystemClock())
138     private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
139     private val wifiPickerTrackerCallback =
140         argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
141     private val vcnTransportInfo = VcnTransportInfo.Builder().build()
142     private val userRepository = kosmos.fakeUserRepository
143 
144     private val testDispatcher = StandardTestDispatcher()
145     private val testScope = TestScope(testDispatcher)
146 
147     private lateinit var underTest: MobileConnectionsRepositoryImpl
148 
149     @Before
150     fun setUp() {
151         MockitoAnnotations.initMocks(this)
152         whenever(telephonyManager.simOperatorName).thenReturn("")
153 
154         // Set up so the individual connection repositories
155         whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
156             telephonyManager.also {
157                 whenever(it.subscriptionId).thenReturn(invocation.getArgument(0))
158             }
159         }
160 
161         whenever(logBufferFactory.getOrCreate(anyString(), anyInt())).thenAnswer { _ ->
162             logcatTableLogBuffer(kosmos, "test")
163         }
164 
165         whenever(
166                 wifiPickerTrackerFactory.create(
167                     any(),
168                     any(),
169                     capture(wifiPickerTrackerCallback),
170                     any(),
171                 )
172             )
173             .thenReturn(wifiPickerTracker)
174 
175         // For convenience, set up the subscription info callbacks
176         whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
177             when (invocation.getArgument(0) as Int) {
178                 1 -> SUB_1
179                 2 -> SUB_2
180                 3 -> SUB_3
181                 4 -> SUB_4
182                 else -> null
183             }
184         }
185 
186         connectivityRepository =
187             ConnectivityRepositoryImpl(
188                 connectivityManager,
189                 ConnectivitySlots(context),
190                 context,
191                 mock(),
192                 mock(),
193                 testScope.backgroundScope,
194                 mock(),
195             )
196 
197         airplaneModeRepository = FakeAirplaneModeRepository()
198 
199         wifiRepository =
200             WifiRepositoryImpl(
201                 mContext,
202                 userRepository,
203                 testScope.backgroundScope,
204                 mainExecutor,
205                 testDispatcher,
206                 wifiPickerTrackerFactory,
207                 wifiManager,
208                 wifiLogBuffer,
209                 wifiTableLogBuffer,
210             )
211 
212         carrierConfigRepository =
213             CarrierConfigRepository(
214                 fakeBroadcastDispatcher,
215                 mock(),
216                 mock(),
217                 logger,
218                 testScope.backgroundScope,
219             )
220 
221         connectionFactory =
222             MobileConnectionRepositoryImpl.Factory(
223                 context,
224                 fakeBroadcastDispatcher,
225                 connectivityManager,
226                 telephonyManager = telephonyManager,
227                 bgDispatcher = testDispatcher,
228                 logger = logger,
229                 mobileMappingsProxy = mobileMappings,
230                 scope = testScope.backgroundScope,
231                 flags = flags,
232                 carrierConfigRepository = carrierConfigRepository,
233             )
234         carrierMergedFactory =
235             CarrierMergedConnectionRepository.Factory(
236                 telephonyManager,
237                 testScope.backgroundScope.coroutineContext,
238                 testScope.backgroundScope,
239                 wifiRepository,
240             )
241         fullConnectionFactory =
242             FullMobileConnectionRepository.Factory(
243                 scope = testScope.backgroundScope,
244                 logFactory = logBufferFactory,
245                 mobileRepoFactory = connectionFactory,
246                 carrierMergedRepoFactory = carrierMergedFactory,
247             )
248 
249         underTest =
250             MobileConnectionsRepositoryImpl(
251                 connectivityRepository,
252                 subscriptionManager,
253                 subscriptionManagerProxy,
254                 telephonyManager,
255                 logger,
256                 summaryLogger,
257                 mobileMappings,
258                 fakeBroadcastDispatcher,
259                 context,
260                 /* bgDispatcher = */ testDispatcher,
261                 testScope.backgroundScope,
262                 /* mainDispatcher = */ testDispatcher,
263                 airplaneModeRepository,
264                 wifiRepository,
265                 fullConnectionFactory,
266                 updateMonitor,
267                 mock(),
268             )
269 
270         testScope.runCurrent()
271     }
272 
273     @Test
274     fun testSubscriptions_initiallyEmpty() =
275         testScope.runTest {
276             assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
277         }
278 
279     @Test
280     fun testSubscriptions_listUpdates() =
281         testScope.runTest {
282             val latest by collectLastValue(underTest.subscriptions)
283 
284             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
285                 .thenReturn(listOf(SUB_1, SUB_2))
286             getSubscriptionCallback().onSubscriptionsChanged()
287 
288             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
289         }
290 
291     @Test
292     fun testSubscriptions_removingSub_updatesList() =
293         testScope.runTest {
294             val latest by collectLastValue(underTest.subscriptions)
295 
296             // WHEN 2 networks show up
297             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
298                 .thenReturn(listOf(SUB_1, SUB_2))
299             getSubscriptionCallback().onSubscriptionsChanged()
300 
301             // WHEN one network is removed
302             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
303                 .thenReturn(listOf(SUB_2))
304             getSubscriptionCallback().onSubscriptionsChanged()
305 
306             // THEN the subscriptions list represents the newest change
307             assertThat(latest).isEqualTo(listOf(MODEL_2))
308         }
309 
310     @Test
311     fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() =
312         testScope.runTest {
313             val latest by collectLastValue(underTest.subscriptions)
314 
315             val onlyNtnSub =
316                 mock<SubscriptionInfo>().also {
317                     whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true)
318                     whenever(it.subscriptionId).thenReturn(45)
319                     whenever(it.groupUuid).thenReturn(GROUP_1)
320                     whenever(it.carrierName).thenReturn("NTN only")
321                     whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
322                 }
323 
324             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
325                 .thenReturn(listOf(onlyNtnSub))
326             getSubscriptionCallback().onSubscriptionsChanged()
327 
328             assertThat(latest).hasSize(1)
329             assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
330         }
331 
332     @Test
333     fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() =
334         testScope.runTest {
335             val latest by collectLastValue(underTest.subscriptions)
336 
337             val notOnlyNtnSub =
338                 mock<SubscriptionInfo>().also {
339                     whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false)
340                     whenever(it.subscriptionId).thenReturn(45)
341                     whenever(it.groupUuid).thenReturn(GROUP_1)
342                     whenever(it.carrierName).thenReturn("NTN only")
343                     whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
344                 }
345 
346             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
347                 .thenReturn(listOf(notOnlyNtnSub))
348             getSubscriptionCallback().onSubscriptionsChanged()
349 
350             assertThat(latest).hasSize(1)
351             assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
352         }
353 
354     @Test
355     fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
356         testScope.runTest {
357             val latest by collectLastValue(underTest.subscriptions)
358 
359             setWifiState(isCarrierMerged = true)
360             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
361                 .thenReturn(listOf(SUB_CM))
362             getSubscriptionCallback().onSubscriptionsChanged()
363 
364             assertThat(latest).isEqualTo(listOf(MODEL_CM))
365         }
366 
367     @Test
368     fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() =
369         testScope.runTest {
370             val latest by collectLastValue(underTest.subscriptions)
371 
372             setWifiState(isCarrierMerged = true)
373             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
374                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
375             getSubscriptionCallback().onSubscriptionsChanged()
376 
377             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
378         }
379 
380     @Test
381     fun testActiveDataSubscriptionId_initialValueIsNull() =
382         testScope.runTest {
383             assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null)
384         }
385 
386     @Test
387     fun testActiveDataSubscriptionId_updates() =
388         testScope.runTest {
389             val active by collectLastValue(underTest.activeMobileDataSubscriptionId)
390 
391             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
392                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
393 
394             assertThat(active).isEqualTo(SUB_2_ID)
395         }
396 
397     @Test
398     fun activeSubId_nullIfInvalidSubIdIsReceived() =
399         testScope.runTest {
400             val latest by collectLastValue(underTest.activeMobileDataSubscriptionId)
401 
402             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
403                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
404 
405             assertThat(latest).isNotNull()
406 
407             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
408                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
409 
410             assertThat(latest).isNull()
411         }
412 
413     @Test
414     fun activeRepo_initiallyNull() {
415         assertThat(underTest.activeMobileDataRepository.value).isNull()
416     }
417 
418     @Test
419     fun activeRepo_updatesWithActiveDataId() =
420         testScope.runTest {
421             val latest by collectLastValue(underTest.activeMobileDataRepository)
422 
423             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
424                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
425 
426             assertThat(latest?.subId).isEqualTo(SUB_2_ID)
427         }
428 
429     @Test
430     fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() =
431         testScope.runTest {
432             val latest by collectLastValue(underTest.activeMobileDataRepository)
433 
434             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
435                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
436 
437             assertThat(latest).isNotNull()
438 
439             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
440                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
441 
442             assertThat(latest).isNull()
443         }
444 
445     @Test
446     /** Regression test for b/268146648. */
447     fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
448         testScope.runTest {
449             val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
450             val subscriptions by collectLastValue(underTest.subscriptions)
451 
452             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
453                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
454 
455             assertThat(subscriptions).isEmpty()
456             assertThat(activeRepo).isNotNull()
457         }
458 
459     @Test
460     fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
461         testScope.runTest {
462             var latestActiveRepo: MobileConnectionRepository? = null
463             collectLastValue(
464                 underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
465                     latestActiveRepo = underTest.getRepoForSubId(it)
466                 }
467             )
468 
469             val latestSubscriptions by collectLastValue(underTest.subscriptions)
470 
471             // Active data subscription id is sent, but no subscription change has been posted yet
472             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
473                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
474 
475             // Subscriptions list is empty
476             assertThat(latestSubscriptions).isEmpty()
477             // getRepoForSubId does not throw
478             assertThat(latestActiveRepo).isNotNull()
479         }
480 
481     @Test
482     fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
483         testScope.runTest {
484             val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
485             collectLastValue(underTest.subscriptions)
486 
487             // GIVEN active repo is updated before the subscription list updates
488             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
489                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
490 
491             assertThat(activeRepo).isNotNull()
492 
493             // GIVEN the subscription list is then updated which includes the active data sub id
494             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
495                 .thenReturn(listOf(SUB_2))
496             getSubscriptionCallback().onSubscriptionsChanged()
497 
498             // WHEN requesting a connection repository for the subscription
499             val newRepo = underTest.getRepoForSubId(SUB_2_ID)
500 
501             // THEN the newly request repo has been cached and reused
502             assertThat(activeRepo).isSameInstanceAs(newRepo)
503         }
504 
505     @Test
506     fun testConnectionRepository_validSubId_isCached() =
507         testScope.runTest {
508             collectLastValue(underTest.subscriptions)
509 
510             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
511                 .thenReturn(listOf(SUB_1))
512             getSubscriptionCallback().onSubscriptionsChanged()
513 
514             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
515             val repo2 = underTest.getRepoForSubId(SUB_1_ID)
516 
517             assertThat(repo1).isSameInstanceAs(repo2)
518         }
519 
520     @Test
521     fun testConnectionRepository_carrierMergedSubId_isCached() =
522         testScope.runTest {
523             collectLastValue(underTest.subscriptions)
524 
525             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
526             setWifiState(isCarrierMerged = true)
527             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
528                 .thenReturn(listOf(SUB_CM))
529             getSubscriptionCallback().onSubscriptionsChanged()
530 
531             val repo1 = underTest.getRepoForSubId(SUB_CM_ID)
532             val repo2 = underTest.getRepoForSubId(SUB_CM_ID)
533 
534             assertThat(repo1).isSameInstanceAs(repo2)
535         }
536 
537     @Test
538     fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
539         testScope.runTest {
540             collectLastValue(underTest.subscriptions)
541 
542             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
543             setWifiState(isCarrierMerged = true)
544             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
545                 .thenReturn(listOf(SUB_1, SUB_CM))
546             getSubscriptionCallback().onSubscriptionsChanged()
547 
548             val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
549             val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
550             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
551             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
552         }
553 
554     @Test
555     fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
556         testScope.runTest {
557             collectLastValue(underTest.subscriptions)
558 
559             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
560             setWifiState(isCarrierMerged = true)
561             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
562                 .thenReturn(listOf(SUB_1, SUB_CM))
563             getSubscriptionCallback().onSubscriptionsChanged()
564 
565             val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
566             var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
567             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
568             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
569 
570             // WHEN the wifi network updates to be not carrier merged
571             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
572             setWifiState(isCarrierMerged = false)
573             runCurrent()
574 
575             // THEN the repos update
576             val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
577             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
578             assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
579             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
580         }
581 
582     @Test
583     fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
584         testScope.runTest {
585             collectLastValue(underTest.subscriptions)
586 
587             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
588             setWifiState(isCarrierMerged = false)
589             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
590                 .thenReturn(listOf(SUB_1, SUB_CM))
591             getSubscriptionCallback().onSubscriptionsChanged()
592             runCurrent()
593 
594             val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
595             var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
596             assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
597             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
598 
599             // WHEN the wifi network updates to be carrier merged
600             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
601             setWifiState(isCarrierMerged = true)
602             runCurrent()
603 
604             // THEN the repos update
605             val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
606             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
607             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
608             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
609         }
610 
611     @SuppressLint("UnspecifiedRegisterReceiverFlag")
612     @Test
613     fun testDeviceEmergencyCallState_eagerlyChecksState() =
614         testScope.runTest {
615             // Value starts out false
616             assertThat(underTest.isDeviceEmergencyCallCapable.value).isFalse()
617             whenever(telephonyManager.activeModemCount).thenReturn(1)
618             whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { _ ->
619                 ServiceState().apply { isEmergencyOnly = true }
620             }
621 
622             // WHEN an appropriate intent gets sent out
623             val intent = serviceStateIntent(subId = -1)
624             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
625             runCurrent()
626 
627             // THEN the repo's state is updated despite no listeners
628             assertThat(underTest.isDeviceEmergencyCallCapable.value).isEqualTo(true)
629         }
630 
631     @Test
632     fun testDeviceEmergencyCallState_aggregatesAcrossSlots_oneTrue() =
633         testScope.runTest {
634             val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
635 
636             // GIVEN there are multiple slots
637             whenever(telephonyManager.activeModemCount).thenReturn(4)
638             // GIVEN only one of them reports ECM
639             whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
640                 when (invocation.getArgument(0) as Int) {
641                     0 -> ServiceState().apply { isEmergencyOnly = false }
642                     1 -> ServiceState().apply { isEmergencyOnly = false }
643                     2 -> ServiceState().apply { isEmergencyOnly = true }
644                     3 -> ServiceState().apply { isEmergencyOnly = false }
645                     else -> null
646                 }
647             }
648 
649             // GIVEN a broadcast goes out for the appropriate subID
650             val intent = serviceStateIntent(subId = -1)
651             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
652             runCurrent()
653 
654             // THEN the device is in ECM, because one of the service states is
655             assertThat(latest).isTrue()
656         }
657 
658     @Test
659     fun testDeviceEmergencyCallState_aggregatesAcrossSlots_allFalse() =
660         testScope.runTest {
661             val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
662 
663             // GIVEN there are multiple slots
664             whenever(telephonyManager.activeModemCount).thenReturn(4)
665             // GIVEN only one of them reports ECM
666             whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
667                 when (invocation.getArgument(0) as Int) {
668                     0 -> ServiceState().apply { isEmergencyOnly = false }
669                     1 -> ServiceState().apply { isEmergencyOnly = false }
670                     2 -> ServiceState().apply { isEmergencyOnly = false }
671                     3 -> ServiceState().apply { isEmergencyOnly = false }
672                     else -> null
673                 }
674             }
675 
676             // GIVEN a broadcast goes out for the appropriate subID
677             val intent = serviceStateIntent(subId = -1)
678             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
679             runCurrent()
680 
681             // THEN the device is in ECM, because one of the service states is
682             assertThat(latest).isFalse()
683         }
684 
685     @Test
686     @Ignore("b/333912012")
687     fun testConnectionCache_clearsInvalidSubscriptions() =
688         testScope.runTest {
689             collectLastValue(underTest.subscriptions)
690 
691             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
692                 .thenReturn(listOf(SUB_1, SUB_2))
693             getSubscriptionCallback().onSubscriptionsChanged()
694 
695             // Get repos to trigger caching
696             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
697             val repo2 = underTest.getRepoForSubId(SUB_2_ID)
698 
699             assertThat(underTest.getSubIdRepoCache())
700                 .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
701 
702             // SUB_2 disappears
703             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
704                 .thenReturn(listOf(SUB_1))
705             getSubscriptionCallback().onSubscriptionsChanged()
706 
707             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
708         }
709 
710     @Test
711     @Ignore("b/333912012")
712     fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
713         testScope.runTest {
714             collectLastValue(underTest.subscriptions)
715 
716             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
717             setWifiState(isCarrierMerged = true)
718             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
719                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
720             getSubscriptionCallback().onSubscriptionsChanged()
721 
722             // Get repos to trigger caching
723             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
724             val repo2 = underTest.getRepoForSubId(SUB_2_ID)
725             val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
726 
727             assertThat(underTest.getSubIdRepoCache())
728                 .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
729 
730             // SUB_2 and SUB_CM disappear
731             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
732                 .thenReturn(listOf(SUB_1))
733             getSubscriptionCallback().onSubscriptionsChanged()
734 
735             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
736         }
737 
738     /** Regression test for b/261706421 */
739     @Test
740     @Ignore("b/333912012")
741     fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
742         testScope.runTest {
743             collectLastValue(underTest.subscriptions)
744 
745             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
746                 .thenReturn(listOf(SUB_1, SUB_2))
747             getSubscriptionCallback().onSubscriptionsChanged()
748 
749             // Get repos to trigger caching
750             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
751             val repo2 = underTest.getRepoForSubId(SUB_2_ID)
752 
753             assertThat(underTest.getSubIdRepoCache())
754                 .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
755 
756             // All subscriptions disappear
757             whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
758             getSubscriptionCallback().onSubscriptionsChanged()
759 
760             assertThat(underTest.getSubIdRepoCache()).isEmpty()
761         }
762 
763     @Test
764     fun testConnectionsCache_keepsReposCached() =
765         testScope.runTest {
766             // Collect subscriptions to start the job
767             collectLastValue(underTest.subscriptions)
768 
769             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
770                 .thenReturn(listOf(SUB_1))
771             getSubscriptionCallback().onSubscriptionsChanged()
772 
773             val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
774 
775             // All subscriptions disappear
776             whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
777             getSubscriptionCallback().onSubscriptionsChanged()
778 
779             // Sub1 comes back
780             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
781                 .thenReturn(listOf(SUB_1))
782             getSubscriptionCallback().onSubscriptionsChanged()
783 
784             val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
785 
786             assertThat(repo1_1).isSameInstanceAs(repo1_2)
787         }
788 
789     @Test
790     fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
791         testScope.runTest {
792             // Collect subscriptions to start the job
793             collectLastValue(underTest.subscriptions)
794 
795             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
796                 .thenReturn(listOf(SUB_1))
797             getSubscriptionCallback().onSubscriptionsChanged()
798 
799             // Client grabs a reference to a repository, but doesn't keep it around
800             underTest.getRepoForSubId(SUB_1_ID)
801 
802             // All subscriptions disappear
803             whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
804             getSubscriptionCallback().onSubscriptionsChanged()
805 
806             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
807 
808             assertThat(repo1).isNotNull()
809         }
810 
811     @Test
812     fun testConnectionRepository_invalidSubId_doesNotThrow() =
813         testScope.runTest {
814             underTest.getRepoForSubId(SUB_1_ID)
815             // No exception
816         }
817 
818     @Test
819     fun connectionRepository_logBufferContainsSubIdInItsName() =
820         testScope.runTest {
821             collectLastValue(underTest.subscriptions)
822 
823             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
824                 .thenReturn(listOf(SUB_1, SUB_2))
825             getSubscriptionCallback().onSubscriptionsChanged()
826 
827             // Get repos to trigger creation
828             underTest.getRepoForSubId(SUB_1_ID)
829             verify(logBufferFactory).getOrCreate(eq(tableBufferLogName(SUB_1_ID)), anyInt())
830             underTest.getRepoForSubId(SUB_2_ID)
831             verify(logBufferFactory).getOrCreate(eq(tableBufferLogName(SUB_2_ID)), anyInt())
832         }
833 
834     @Test
835     fun testDefaultDataSubId_updatesOnBroadcast() =
836         testScope.runTest {
837             val latest by collectLastValue(underTest.defaultDataSubId)
838 
839             assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
840 
841             val intent2 =
842                 Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
843                     .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
844             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent2)
845 
846             assertThat(latest).isEqualTo(SUB_2_ID)
847 
848             val intent1 =
849                 Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
850                     .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
851             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent1)
852 
853             assertThat(latest).isEqualTo(SUB_1_ID)
854         }
855 
856     @Test
857     fun defaultDataSubId_fetchesInitialValueOnStart() =
858         testScope.runTest {
859             subscriptionManagerProxy.defaultDataSubId = 2
860             val latest by collectLastValue(underTest.defaultDataSubId)
861 
862             assertThat(latest).isEqualTo(2)
863         }
864 
865     @Test
866     fun defaultDataSubId_fetchesCurrentOnRestart() =
867         testScope.runTest {
868             subscriptionManagerProxy.defaultDataSubId = 2
869             var latest: Int? = null
870             var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
871             runCurrent()
872 
873             assertThat(latest).isEqualTo(2)
874 
875             job.cancel()
876 
877             // Collectors go away but come back later
878 
879             latest = null
880 
881             subscriptionManagerProxy.defaultDataSubId = 1
882 
883             job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
884             runCurrent()
885 
886             assertThat(latest).isEqualTo(1)
887 
888             job.cancel()
889         }
890 
891     @Test
892     fun mobileIsDefault_startsAsFalse() {
893         assertThat(underTest.mobileIsDefault.value).isFalse()
894     }
895 
896     @Test
897     fun mobileIsDefault_capsHaveCellular_isDefault() =
898         testScope.runTest {
899             val caps =
900                 mock<NetworkCapabilities>().also {
901                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
902                 }
903 
904             val latest by collectLastValue(underTest.mobileIsDefault)
905 
906             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
907 
908             assertThat(latest).isTrue()
909         }
910 
911     @Test
912     fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() =
913         testScope.runTest {
914             val caps =
915                 mock<NetworkCapabilities>().also {
916                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
917                 }
918 
919             val latest by collectLastValue(underTest.mobileIsDefault)
920 
921             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
922 
923             assertThat(latest).isFalse()
924         }
925 
926     @Test
927     fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
928         testScope.runTest {
929             val carrierMergedInfo =
930                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
931             val caps =
932                 mock<NetworkCapabilities>().also {
933                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
934                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
935                 }
936 
937             val latest by collectLastValue(underTest.mobileIsDefault)
938 
939             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
940 
941             assertThat(latest).isTrue()
942         }
943 
944     @Test
945     fun mobileIsDefault_wifiDefault_mobileNotDefault() =
946         testScope.runTest {
947             val caps =
948                 mock<NetworkCapabilities>().also {
949                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
950                 }
951 
952             val latest by collectLastValue(underTest.mobileIsDefault)
953 
954             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
955 
956             assertThat(latest).isFalse()
957         }
958 
959     @Test
960     fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
961         testScope.runTest {
962             val caps =
963                 mock<NetworkCapabilities>().also {
964                     whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
965                 }
966 
967             val latest by collectLastValue(underTest.mobileIsDefault)
968 
969             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
970 
971             assertThat(latest).isFalse()
972         }
973 
974     /** Regression test for b/272586234. */
975     @Test
976     fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
977         testScope.runTest {
978             val carrierMergedInfo =
979                 mock<WifiInfo>().apply {
980                     whenever(this.isCarrierMerged).thenReturn(true)
981                     whenever(this.isPrimary).thenReturn(true)
982                 }
983             val caps =
984                 mock<NetworkCapabilities>().also {
985                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
986                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
987                 }
988 
989             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
990 
991             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
992             setWifiState(isCarrierMerged = true)
993 
994             assertThat(latest).isTrue()
995         }
996 
997     @Test
998     fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
999         testScope.runTest {
1000             val carrierMergedInfo =
1001                 mock<WifiInfo>().apply {
1002                     whenever(this.isCarrierMerged).thenReturn(true)
1003                     whenever(this.isPrimary).thenReturn(true)
1004                 }
1005             val caps =
1006                 mock<NetworkCapabilities>().also {
1007                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1008                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
1009                 }
1010 
1011             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1012 
1013             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1014             setWifiState(isCarrierMerged = true)
1015 
1016             assertThat(latest).isTrue()
1017         }
1018 
1019     private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
1020         val network = mock<Network>()
1021         val capabilities =
1022             mock<NetworkCapabilities>().also {
1023                 whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1024                 whenever(it.transportInfo).thenReturn(wifiInfo)
1025             }
1026         whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
1027 
1028         return network
1029     }
1030 
1031     /** Regression test for b/272586234. */
1032     @Test
1033     fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
1034         testScope.runTest {
1035             val carrierMergedInfo =
1036                 mock<WifiInfo>().apply {
1037                     whenever(this.isCarrierMerged).thenReturn(true)
1038                     whenever(this.isPrimary).thenReturn(true)
1039                 }
1040             val underlyingWifi = newWifiNetwork(carrierMergedInfo)
1041             val caps =
1042                 mock<NetworkCapabilities>().also {
1043                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1044                     whenever(it.transportInfo).thenReturn(vcnTransportInfo)
1045                     whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
1046                 }
1047 
1048             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1049 
1050             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1051             setWifiState(isCarrierMerged = true)
1052 
1053             assertThat(latest).isTrue()
1054         }
1055 
1056     @Test
1057     fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
1058         testScope.runTest {
1059             val carrierMergedInfo =
1060                 mock<WifiInfo>().apply {
1061                     whenever(this.isCarrierMerged).thenReturn(true)
1062                     whenever(this.isPrimary).thenReturn(true)
1063                 }
1064             val underlyingWifi = newWifiNetwork(carrierMergedInfo)
1065             val caps =
1066                 mock<NetworkCapabilities>().also {
1067                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1068                     whenever(it.transportInfo).thenReturn(vcnTransportInfo)
1069                     whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
1070                 }
1071 
1072             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1073 
1074             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1075             setWifiState(isCarrierMerged = true)
1076 
1077             assertThat(latest).isTrue()
1078         }
1079 
1080     @Test
1081     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
1082         testScope.runTest {
1083             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1084 
1085             val underlyingNetwork = mock<Network>()
1086             val carrierMergedInfo =
1087                 mock<WifiInfo>().apply {
1088                     whenever(this.isCarrierMerged).thenReturn(true)
1089                     whenever(this.isPrimary).thenReturn(true)
1090                 }
1091             val underlyingWifiCapabilities =
1092                 mock<NetworkCapabilities>().also {
1093                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1094                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
1095                 }
1096             whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
1097                 .thenReturn(underlyingWifiCapabilities)
1098 
1099             // WHEN the main capabilities have an underlying carrier merged network via WIFI
1100             // transport and WifiInfo
1101             val mainCapabilities =
1102                 mock<NetworkCapabilities>().also {
1103                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1104                     whenever(it.transportInfo).thenReturn(null)
1105                     whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
1106                 }
1107 
1108             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
1109             setWifiState(isCarrierMerged = true)
1110 
1111             // THEN there's a carrier merged connection
1112             assertThat(latest).isTrue()
1113         }
1114 
1115     @Test
1116     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
1117         testScope.runTest {
1118             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1119 
1120             val underlyingCarrierMergedNetwork = mock<Network>()
1121             val carrierMergedInfo =
1122                 mock<WifiInfo>().apply {
1123                     whenever(this.isCarrierMerged).thenReturn(true)
1124                     whenever(this.isPrimary).thenReturn(true)
1125                 }
1126 
1127             // The Wifi network that is under the VCN network
1128             val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
1129 
1130             val underlyingCapabilities =
1131                 mock<NetworkCapabilities>().also {
1132                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1133                     whenever(it.transportInfo).thenReturn(vcnTransportInfo)
1134                     whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
1135                 }
1136             whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
1137                 .thenReturn(underlyingCapabilities)
1138 
1139             // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
1140             // transport and VcnTransportInfo
1141             val mainCapabilities =
1142                 mock<NetworkCapabilities>().also {
1143                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1144                     whenever(it.transportInfo).thenReturn(null)
1145                     whenever(it.underlyingNetworks)
1146                         .thenReturn(listOf(underlyingCarrierMergedNetwork))
1147                 }
1148 
1149             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
1150             setWifiState(isCarrierMerged = true)
1151 
1152             // THEN there's a carrier merged connection
1153             assertThat(latest).isTrue()
1154         }
1155 
1156     /** Regression test for b/272586234. */
1157     @Test
1158     fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() =
1159         testScope.runTest {
1160             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1161 
1162             // WHEN the default callback is TRANSPORT_WIFI but not carrier merged
1163             val carrierMergedInfo =
1164                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
1165             val caps =
1166                 mock<NetworkCapabilities>().also {
1167                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1168                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
1169                 }
1170             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1171 
1172             // BUT the wifi repo has gotten updates that it *is* carrier merged
1173             setWifiState(isCarrierMerged = true)
1174 
1175             // THEN hasCarrierMergedConnection is true
1176             assertThat(latest).isTrue()
1177         }
1178 
1179     /** Regression test for b/278618530. */
1180     @Test
1181     fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() =
1182         testScope.runTest {
1183             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1184 
1185             // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
1186             val caps =
1187                 mock<NetworkCapabilities>().also {
1188                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1189                     whenever(it.transportInfo).thenReturn(null)
1190                 }
1191             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1192 
1193             // BUT the wifi repo has gotten updates that it *is* carrier merged
1194             setWifiState(isCarrierMerged = true)
1195 
1196             // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
1197             // takes precedence over the wifi network being carrier merged.)
1198             assertThat(latest).isFalse()
1199         }
1200 
1201     /** Regression test for b/278618530. */
1202     @Test
1203     fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() =
1204         testScope.runTest {
1205             val latest by collectLastValue(underTest.hasCarrierMergedConnection)
1206 
1207             // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
1208             val caps =
1209                 mock<NetworkCapabilities>().also {
1210                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
1211                     whenever(it.transportInfo).thenReturn(null)
1212                 }
1213             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1214 
1215             // BUT the wifi repo has gotten updates that it *is* carrier merged
1216             setWifiState(isCarrierMerged = true)
1217             // AND we're in airplane mode
1218             airplaneModeRepository.setIsAirplaneMode(true)
1219 
1220             // THEN hasCarrierMergedConnection is true.
1221             assertThat(latest).isTrue()
1222         }
1223 
1224     @Test
1225     fun defaultConnectionIsValidated_startsAsFalse() {
1226         assertThat(underTest.defaultConnectionIsValidated.value).isFalse()
1227     }
1228 
1229     @Test
1230     fun defaultConnectionIsValidated_capsHaveValidated_isValidated() =
1231         testScope.runTest {
1232             val caps =
1233                 mock<NetworkCapabilities>().also {
1234                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
1235                 }
1236 
1237             val latest by collectLastValue(underTest.defaultConnectionIsValidated)
1238 
1239             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1240 
1241             assertThat(latest).isTrue()
1242         }
1243 
1244     @Test
1245     fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() =
1246         testScope.runTest {
1247             val caps =
1248                 mock<NetworkCapabilities>().also {
1249                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false)
1250                 }
1251 
1252             val latest by collectLastValue(underTest.defaultConnectionIsValidated)
1253 
1254             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
1255 
1256             assertThat(latest).isFalse()
1257         }
1258 
1259     @Test
1260     fun config_initiallyFromContext() =
1261         testScope.runTest {
1262             overrideResource(R.bool.config_showMin3G, true)
1263             val configFromContext = MobileMappings.Config.readConfig(context)
1264             assertThat(configFromContext.showAtLeast3G).isTrue()
1265 
1266             // The initial value will be fetched when the repo is created, so we need to override
1267             // the resources and then re-create the repo.
1268             underTest =
1269                 MobileConnectionsRepositoryImpl(
1270                     connectivityRepository,
1271                     subscriptionManager,
1272                     subscriptionManagerProxy,
1273                     telephonyManager,
1274                     logger,
1275                     summaryLogger,
1276                     mobileMappings,
1277                     fakeBroadcastDispatcher,
1278                     context,
1279                     testDispatcher,
1280                     testScope.backgroundScope,
1281                     testDispatcher,
1282                     airplaneModeRepository,
1283                     wifiRepository,
1284                     fullConnectionFactory,
1285                     updateMonitor,
1286                     mock(),
1287                 )
1288 
1289             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
1290 
1291             assertTrue(latest!!.areEqual(configFromContext))
1292             assertTrue(latest!!.showAtLeast3G)
1293         }
1294 
1295     @Test
1296     fun config_subIdChangeEvent_updated() =
1297         testScope.runTest {
1298             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
1299 
1300             assertThat(latest!!.showAtLeast3G).isFalse()
1301 
1302             overrideResource(R.bool.config_showMin3G, true)
1303             val configFromContext = MobileMappings.Config.readConfig(context)
1304             assertThat(configFromContext.showAtLeast3G).isTrue()
1305 
1306             // WHEN the change event is fired
1307             val intent =
1308                 Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
1309                     .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
1310             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
1311 
1312             // THEN the config is updated
1313             assertTrue(latest!!.areEqual(configFromContext))
1314             assertTrue(latest!!.showAtLeast3G)
1315         }
1316 
1317     @Test
1318     fun config_carrierConfigChangeEvent_updated() =
1319         testScope.runTest {
1320             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
1321 
1322             assertThat(latest!!.showAtLeast3G).isFalse()
1323 
1324             overrideResource(R.bool.config_showMin3G, true)
1325             val configFromContext = MobileMappings.Config.readConfig(context)
1326             assertThat(configFromContext.showAtLeast3G).isTrue()
1327 
1328             // WHEN the change event is fired
1329             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
1330                 context,
1331                 Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
1332             )
1333 
1334             // THEN the config is updated
1335             assertThat(latest!!.areEqual(configFromContext)).isTrue()
1336             assertThat(latest!!.showAtLeast3G).isTrue()
1337         }
1338 
1339     @Test
1340     fun carrierConfig_initialValueIsFetched() =
1341         testScope.runTest {
1342             // Value starts out false
1343             assertThat(underTest.defaultDataSubRatConfig.value.showAtLeast3G).isFalse()
1344 
1345             overrideResource(R.bool.config_showMin3G, true)
1346             val configFromContext = MobileMappings.Config.readConfig(context)
1347             assertThat(configFromContext.showAtLeast3G).isTrue()
1348 
1349             // WHEN the change event is fired
1350             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
1351                 context,
1352                 Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
1353             )
1354 
1355             // WHEN collection starts AFTER the broadcast is sent out
1356             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
1357 
1358             // THEN the config has the updated value
1359             assertThat(latest!!.areEqual(configFromContext)).isTrue()
1360             assertThat(latest!!.showAtLeast3G).isTrue()
1361         }
1362 
1363     @Test
1364     fun activeDataChange_inSameGroup_emitsUnit() =
1365         testScope.runTest {
1366             val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
1367 
1368             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
1369                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
1370             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
1371                 .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
1372 
1373             assertThat(latest).isEqualTo(Unit)
1374         }
1375 
1376     @Test
1377     fun activeDataChange_notInSameGroup_doesNotEmit() =
1378         testScope.runTest {
1379             val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
1380 
1381             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
1382                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
1383             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
1384                 .onActiveDataSubscriptionIdChanged(SUB_1_ID)
1385 
1386             assertThat(latest).isEqualTo(null)
1387         }
1388 
1389     @Test
1390     fun anySimSecure_propagatesStateFromKeyguardUpdateMonitor() =
1391         testScope.runTest {
1392             val latest by collectLastValue(underTest.isAnySimSecure)
1393             assertThat(latest).isFalse()
1394 
1395             val updateMonitorCallback = argumentCaptor<KeyguardUpdateMonitorCallback>()
1396             verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
1397 
1398             whenever(updateMonitor.isSimPinSecure).thenReturn(true)
1399             updateMonitorCallback.value.onSimStateChanged(0, 0, 0)
1400 
1401             assertThat(latest).isTrue()
1402 
1403             whenever(updateMonitor.isSimPinSecure).thenReturn(false)
1404             updateMonitorCallback.value.onSimStateChanged(0, 0, 0)
1405 
1406             assertThat(latest).isFalse()
1407         }
1408 
1409     @Test
1410     fun getIsAnySimSecure_delegatesCallToKeyguardUpdateMonitor() =
1411         testScope.runTest {
1412             assertThat(underTest.getIsAnySimSecure()).isFalse()
1413 
1414             whenever(updateMonitor.isSimPinSecure).thenReturn(true)
1415 
1416             assertThat(underTest.getIsAnySimSecure()).isTrue()
1417         }
1418 
1419     @Test
1420     fun noSubscriptionsInEcmMode_notInEcmMode() =
1421         testScope.runTest {
1422             whenever(telephonyManager.emergencyCallbackMode).thenReturn(false)
1423 
1424             runCurrent()
1425 
1426             assertThat(underTest.isInEcmMode()).isFalse()
1427         }
1428 
1429     @Test
1430     fun someSubscriptionsInEcmMode_inEcmMode() =
1431         testScope.runTest {
1432             whenever(telephonyManager.emergencyCallbackMode).thenReturn(true)
1433 
1434             runCurrent()
1435 
1436             assertThat(underTest.isInEcmMode()).isTrue()
1437         }
1438 
1439     private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
1440         runCurrent()
1441         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
1442         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
1443         return callbackCaptor.value!!
1444     }
1445 
1446     private fun setWifiState(isCarrierMerged: Boolean) {
1447         if (isCarrierMerged) {
1448             val mergedEntry =
1449                 mock<MergedCarrierEntry>().apply {
1450                     whenever(this.isPrimaryNetwork).thenReturn(true)
1451                     whenever(this.isDefaultNetwork).thenReturn(true)
1452                     whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
1453                 }
1454             whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
1455             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
1456         } else {
1457             val wifiEntry =
1458                 mock<WifiEntry>().apply {
1459                     whenever(this.isPrimaryNetwork).thenReturn(true)
1460                     whenever(this.isDefaultNetwork).thenReturn(true)
1461                 }
1462             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
1463             whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
1464         }
1465         wifiPickerTrackerCallback.value.onWifiEntriesChanged()
1466     }
1467 
1468     private fun TestScope.getSubscriptionCallback():
1469         SubscriptionManager.OnSubscriptionsChangedListener {
1470         runCurrent()
1471         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
1472         verify(subscriptionManager)
1473             .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
1474         return callbackCaptor.value!!
1475     }
1476 
1477     private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
1478         runCurrent()
1479         val callbackCaptor = argumentCaptor<TelephonyCallback>()
1480         verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
1481         return callbackCaptor.allValues
1482     }
1483 
1484     private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
1485         val cbs = this.getTelephonyCallbacks().filterIsInstance<T>()
1486         assertThat(cbs.size).isEqualTo(1)
1487         return cbs[0]
1488     }
1489 
1490     companion object {
1491         // Subscription 1
1492         private const val SUB_1_ID = 1
1493         private const val SUB_1_NAME = "Carrier $SUB_1_ID"
1494         private val GROUP_1 = ParcelUuid(UUID.randomUUID())
1495         private val SUB_1 =
1496             mock<SubscriptionInfo>().also {
1497                 whenever(it.subscriptionId).thenReturn(SUB_1_ID)
1498                 whenever(it.groupUuid).thenReturn(GROUP_1)
1499                 whenever(it.carrierName).thenReturn(SUB_1_NAME)
1500                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
1501             }
1502         private val MODEL_1 =
1503             SubscriptionModel(
1504                 subscriptionId = SUB_1_ID,
1505                 groupUuid = GROUP_1,
1506                 carrierName = SUB_1_NAME,
1507                 profileClass = PROFILE_CLASS_UNSET,
1508             )
1509 
1510         // Subscription 2
1511         private const val SUB_2_ID = 2
1512         private const val SUB_2_NAME = "Carrier $SUB_2_ID"
1513         private val GROUP_2 = ParcelUuid(UUID.randomUUID())
1514         private val SUB_2 =
1515             mock<SubscriptionInfo>().also {
1516                 whenever(it.subscriptionId).thenReturn(SUB_2_ID)
1517                 whenever(it.groupUuid).thenReturn(GROUP_2)
1518                 whenever(it.carrierName).thenReturn(SUB_2_NAME)
1519                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
1520             }
1521         private val MODEL_2 =
1522             SubscriptionModel(
1523                 subscriptionId = SUB_2_ID,
1524                 groupUuid = GROUP_2,
1525                 carrierName = SUB_2_NAME,
1526                 profileClass = PROFILE_CLASS_UNSET,
1527             )
1528 
1529         // Subs 3 and 4 are considered to be in the same group ------------------------------------
1530         private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
1531 
1532         // Subscription 3
1533         private const val SUB_3_ID_GROUPED = 3
1534         private val SUB_3 =
1535             mock<SubscriptionInfo>().also {
1536                 whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED)
1537                 whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
1538                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
1539             }
1540 
1541         // Subscription 4
1542         private const val SUB_4_ID_GROUPED = 4
1543         private val SUB_4 =
1544             mock<SubscriptionInfo>().also {
1545                 whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED)
1546                 whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
1547                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
1548             }
1549 
1550         // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1551 
1552         private const val NET_ID = 123
1553         private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
1554 
1555         // Carrier merged subscription
1556         private const val SUB_CM_ID = 5
1557         private const val SUB_CM_NAME = "Carrier $SUB_CM_ID"
1558         private val SUB_CM =
1559             mock<SubscriptionInfo>().also {
1560                 whenever(it.subscriptionId).thenReturn(SUB_CM_ID)
1561                 whenever(it.carrierName).thenReturn(SUB_CM_NAME)
1562                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
1563             }
1564         private val MODEL_CM =
1565             SubscriptionModel(
1566                 subscriptionId = SUB_CM_ID,
1567                 carrierName = SUB_CM_NAME,
1568                 profileClass = PROFILE_CLASS_UNSET,
1569             )
1570 
1571         private val WIFI_INFO_CM =
1572             mock<WifiInfo>().apply {
1573                 whenever(this.isPrimary).thenReturn(true)
1574                 whenever(this.isCarrierMerged).thenReturn(true)
1575                 whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
1576             }
1577         private val WIFI_NETWORK_CAPS_CM =
1578             mock<NetworkCapabilities>().also {
1579                 whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1580                 whenever(it.transportInfo).thenReturn(WIFI_INFO_CM)
1581                 whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
1582             }
1583 
1584         private val WIFI_INFO_ACTIVE =
1585             mock<WifiInfo>().apply {
1586                 whenever(this.isPrimary).thenReturn(true)
1587                 whenever(this.isCarrierMerged).thenReturn(false)
1588             }
1589         private val WIFI_NETWORK_CAPS_ACTIVE =
1590             mock<NetworkCapabilities>().also {
1591                 whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
1592                 whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
1593                 whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
1594             }
1595 
1596         /**
1597          * To properly mimic telephony manager, create a service state, and then turn it into an
1598          * intent
1599          */
1600         private fun serviceStateIntent(subId: Int): Intent {
1601             return Intent(Intent.ACTION_SERVICE_STATE).apply {
1602                 putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
1603             }
1604         }
1605     }
1606 }
1607