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