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