From ca5ed2ceb0be561cc6b55c230199c2fd9a10f4f3 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Tue, 28 Mar 2023 18:33:15 +0000 Subject: [PATCH 1/2] [Status bar] Take underlying networks into account for wifi. Bug: 225902574 Test: atest ConnectivityRepositoryImplTest WifiRepositoryImplTest MobileConnectionsRepositoryTest Change-Id: Ie576d9ce4f6babf62ff231a4f7d769663348e977 --- .../data/repository/ConnectivityRepository.kt | 44 +- .../repository/prod/WifiRepositoryImpl.kt | 3 +- .../prod/MobileConnectionsRepositoryTest.kt | 74 ++++ .../ConnectivityRepositoryImplTest.kt | 379 ++++++++++++++++++ .../repository/prod/WifiRepositoryImplTest.kt | 146 ++++++- 5 files changed, 634 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index 6479f3d9f8a6f..731f1e0284703 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -44,11 +44,11 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnecti import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.tuner.TunerService import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -68,12 +68,12 @@ interface ConnectivityRepository { val defaultConnections: StateFlow } -@OptIn(ExperimentalCoroutinesApi::class) +@SuppressLint("MissingPermission") @SysUISingleton class ConnectivityRepositoryImpl @Inject constructor( - connectivityManager: ConnectivityManager, + private val connectivityManager: ConnectivityManager, private val connectivitySlots: ConnectivitySlots, context: Context, dumpManager: DumpManager, @@ -144,15 +144,14 @@ constructor( ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + val isWifiDefault = - networkCapabilities.hasTransport(TRANSPORT_WIFI) || - networkCapabilities.getMainOrUnderlyingWifiInfo() != null + networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) - val isCarrierMergedDefault = - networkCapabilities - .getMainOrUnderlyingWifiInfo() - ?.isCarrierMerged == true + val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) @@ -209,7 +208,32 @@ constructor( * always use [WifiInfo] if it's available, so we need to check the underlying transport * info. */ - fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? { + fun NetworkCapabilities.getMainOrUnderlyingWifiInfo( + connectivityManager: ConnectivityManager, + ): WifiInfo? { + val mainWifiInfo = this.getMainWifiInfo() + if (mainWifiInfo != null) { + return mainWifiInfo + } + // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI, + // so skip the underlying network check if it's not CELLULAR. + if (!this.hasTransport(TRANSPORT_CELLULAR)) { + return mainWifiInfo + } + + // Some connections, like VPN connections, may have underlying networks that are + // eventually traced to a wifi or carrier merged connection. So, check those underlying + // networks for possible wifi information as well. See b/225902574. + return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork -> + connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo() + } + } + + /** + * Checks the network capabilities for wifi info, but does *not* check the underlying + * networks. See [getMainOrUnderlyingWifiInfo]. + */ + private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? { // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for // virtual networks like VCN. val canHaveWifiInfo = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index f80aa688268fa..b37c44a2f8cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -138,7 +138,8 @@ constructor( wifiNetworkChangeEvents.tryEmit(Unit) - val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo() + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) if (wifiInfo?.isPrimary == true) { val wifiNetworkModel = createWifiNetworkModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index d65277f37ec48..a259074a7a79d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -75,6 +75,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -812,6 +813,79 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun mobileIsDefault_isCarrierMergedViaUnderlyingWifi_isDefault() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + val underlyingNetwork = mock() + val carrierMergedInfo = + mock().apply { + whenever(this.isCarrierMerged).thenReturn(true) + } + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN carrier merged is default, so mobile is default + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun mobileIsDefault_isCarrierMergedViaUnderlyingCellular_isDefault() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever( + connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork) + ) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN carrier merged is default, so mobile is default + assertThat(latest).isTrue() + + job.cancel() + } + @Test fun mobileIsDefault_wifiDefault_mobileNotDefault() = runBlocking(IMMEDIATE) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 87d4f5c618a73..661002d275b0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -490,6 +491,111 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun defaultConnections_nullUnderlyingInfo_noError() { + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(null) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + // No assert, just verify no error + } + + @Test + fun defaultConnections_underlyingInfoHasNullCapabilities_noError() { + val underlyingNetworkWithNull = mock() + whenever(connectivityManager.getNetworkCapabilities(underlyingNetworkWithNull)) + .thenReturn(null) + + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetworkWithNull)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + // No assert, just verify no error + } + + // This test verifies our internal API for completeness, but we don't expect this case to ever + // happen in practice. + @Test + fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() = + testScope.runTest { + var latest: DefaultConnectionModel? = null + val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this) + + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + assertThat(latest!!.mobile.isDefault).isTrue() + assertThat(latest!!.carrierMerged.isDefault).isTrue() + assertThat(latest!!.wifi.isDefault).isTrue() + + job.cancel() + } + + /** Test for b/225902574. */ + @Test + fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() = + testScope.runTest { + var latest: DefaultConnectionModel? = null + val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this) + + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + assertThat(latest!!.mobile.isDefault).isTrue() + assertThat(latest!!.carrierMerged.isDefault).isTrue() + assertThat(latest!!.wifi.isDefault).isTrue() + + job.cancel() + } + @Test fun defaultConnections_multipleTransports_multipleDefault() = testScope.runTest { @@ -548,6 +654,279 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { + val wifiInfo = mock() + val capabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiInfo) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() { + val wifiInfo = mock() + val vcnInfo = VcnTransportInfo(wifiInfo) + val capabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellularOrWifiTransport_noInfo() { + val capabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(mock()) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingWifi_hasInfo() { + val underlyingNetwork = mock() + val underlyingWifiInfo = mock() + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we fetch the underlying wifi info + assertThat(result).isEqualTo(underlyingWifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() { + val underlyingNetwork = mock() + val underlyingWifiInfo = mock() + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network but is *not* CELLULAR + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we DON'T fetch the underlying wifi info + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() { + val wifiInfo = mock() + val underlyingNetwork = mock() + val underlyingVcnInfo = VcnTransportInfo(wifiInfo) + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying VCN network with wifi + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we fetch the wifi info + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() { + val underlyingNetwork = mock() + val underlyingVcnInfo = VcnTransportInfo(mock()) + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network but it is *not* CELLULAR + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we DON'T fetch the underlying wifi info + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingCellularWithCarrierMerged_hasInfo() { + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(carrierMergedInfo) + assertThat(result!!.isCarrierMerged).isTrue() + } + + @Test + fun getMainOrUnderlyingWifiInfo_multipleUnderlying_usesFirstNonNull() { + // First underlying: Not wifi + val underlyingNotWifiNetwork = mock() + val underlyingNotWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNotWifiNetwork)) + .thenReturn(underlyingNotWifiCapabilities) + + // Second underlying: wifi + val underlyingWifiNetwork1 = mock() + val underlyingWifiInfo1 = mock() + val underlyingWifiCapabilities1 = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo1) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork1)) + .thenReturn(underlyingWifiCapabilities1) + + // Third underlying: also wifi + val underlyingWifiNetwork2 = mock() + val underlyingWifiInfo2 = mock() + val underlyingWifiCapabilities2 = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo2) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork2)) + .thenReturn(underlyingWifiCapabilities2) + + // WHEN the main capabilities has multiple underlying networks + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn( + listOf( + underlyingNotWifiNetwork, + underlyingWifiNetwork1, + underlyingWifiNetwork2, + ) + ) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN the first wifi one is used + assertThat(result).isEqualTo(underlyingWifiInfo1) + } + + @Test + fun getMainOrUnderlyingWifiInfo_nestedUnderlying_doesNotLookAtNested() { + // WHEN there are two layers of underlying networks... + + // Nested network + val nestedUnderlyingNetwork = mock() + val nestedWifiInfo = mock() + val nestedCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(nestedWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(nestedUnderlyingNetwork)) + .thenReturn(nestedCapabilities) + + // Underlying network containing the nested network + val underlyingNetwork = mock() + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(nestedUnderlyingNetwork)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network containing the underlying network, which contains the nested network + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN only the first layer is checked, and the first layer has no wifi info + assertThat(result).isNull() + } + private fun createAndSetRepo() { underTest = ConnectivityRepositoryImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index f69e9a39909bd..ddc6d484d93f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -400,7 +400,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() = + fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() = runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) @@ -436,6 +436,75 @@ class WifiRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val underlyingNetwork = mock() + val carrierMergedInfo = + mock().apply { + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + } + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged, so wifi is default + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged, so wifi is default + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + @Test fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) { @@ -510,6 +579,81 @@ class WifiRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val underlyingNetwork = mock() + val carrierMergedInfo = + mock().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + + job.cancel() + } + @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = runBlocking(IMMEDIATE) { From 4cb3c716cb5031faa1c4b862b4dfb740df751888 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Tue, 4 Apr 2023 20:30:20 +0000 Subject: [PATCH 2/2] [SB Refactor] Use carrierMergedSubId for the mobile default calculation. Bug: 272586234 Test: atest MobileConnectionsRepositoryTest MobileIconsInteractorTest Change-Id: I05fa65f3379b2d8bb197d49a47e894ee160138f3 --- .../repository/MobileConnectionsRepository.kt | 7 + .../repository/MobileRepositorySwitcher.kt | 9 + .../demo/DemoMobileConnectionsRepository.kt | 3 + .../prod/MobileConnectionsRepositoryImpl.kt | 23 +- .../interactor/MobileIconsInteractor.kt | 17 +- .../FakeMobileConnectionsRepository.kt | 2 + .../prod/MobileConnectionsRepositoryTest.kt | 329 ++++++++++-------- .../interactor/MobileIconsInteractorTest.kt | 47 ++- 8 files changed, 291 insertions(+), 146 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index fa712872eb134..ea77163f05564 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -60,6 +60,13 @@ interface MobileConnectionsRepository { */ val mobileIsDefault: StateFlow + /** + * True if the device currently has a carrier merged connection. + * + * See [CarrierMergedConnectionRepository] for more info. + */ + val hasCarrierMergedConnection: Flow + /** True if the default network connection is validated and false otherwise. */ val defaultConnectionIsValidated: StateFlow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index 44b5b3fa25911..eb20bba0d21f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -159,6 +159,15 @@ constructor( .flatMapLatest { it.mobileIsDefault } .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value) + override val hasCarrierMergedConnection: StateFlow = + activeRepo + .flatMapLatest { it.hasCarrierMergedConnection } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + realRepository.hasCarrierMergedConnection.value, + ) + override val defaultConnectionIsValidated: StateFlow = activeRepo .flatMapLatest { it.defaultConnectionIsValidated } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 737bc6826d08b..0e4ceebcc8548 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -159,6 +159,9 @@ constructor( // TODO(b/261029387): not yet supported override val mobileIsDefault: StateFlow = MutableStateFlow(true) + // TODO(b/261029387): not yet supported + override val hasCarrierMergedConnection = MutableStateFlow(false) + // TODO(b/261029387): not yet supported override val defaultConnectionIsValidated: StateFlow = MutableStateFlow(true) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 8c93bf7c21985..71928b8beb52c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -58,6 +58,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -257,18 +258,32 @@ constructor( override val mobileIsDefault: StateFlow = connectivityRepository.defaultConnections - // Because carrier merged networks are displayed as mobile networks, they're - // part of the `isDefault` calculation. See b/272586234. - .map { it.mobile.isDefault || it.carrierMerged.isDefault } + .map { it.mobile.isDefault } .distinctUntilChanged() .logDiffsForTable( tableLogger, - columnPrefix = "", + columnPrefix = LOGGING_PREFIX, columnName = "mobileIsDefault", initialValue = false, ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val hasCarrierMergedConnection: StateFlow = + combine( + connectivityRepository.defaultConnections, + carrierMergedSubId, + ) { defaultConnections, carrierMergedSubId -> + defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogger, + columnPrefix = LOGGING_PREFIX, + columnName = "hasCarrierMergedConnection", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val defaultConnectionIsValidated: StateFlow = connectivityRepository.defaultConnections .map { it.isValidated } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index 6c8310ac3d294..a39bd45a6b7a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -114,7 +114,22 @@ constructor( @Application private val scope: CoroutineScope, ) : MobileIconsInteractor { - override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault + override val mobileIsDefault = + combine( + mobileConnectionsRepo.mobileIsDefault, + mobileConnectionsRepo.hasCarrierMergedConnection, + ) { mobileIsDefault, hasCarrierMergedConnection -> + // Because carrier merged networks are displayed as mobile networks, they're part of + // the `isDefault` calculation. See b/272586234. + mobileIsDefault || hasCarrierMergedConnection + } + .logDiffsForTable( + tableLogger, + LOGGING_PREFIX, + columnName = "mobileIsDefault", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val activeDataConnectionHasDataEnabled: StateFlow = mobileConnectionsRepo.activeMobileDataRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index f9c72d5236738..3591c17403295 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -67,6 +67,8 @@ class FakeMobileConnectionsRepository( override val mobileIsDefault = MutableStateFlow(false) + override val hasCarrierMergedConnection = MutableStateFlow(false) + override val defaultConnectionIsValidated = MutableStateFlow(false) private val subIdRepos = mutableMapOf() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index a259074a7a79d..c40e9237bdb7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -67,6 +67,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.After import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue @@ -75,7 +76,6 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -727,28 +727,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } - /** Regression test for b/272586234. */ - @Test - fun mobileIsDefault_carrierMergedViaWifi_isDefault() = - runBlocking(IMMEDIATE) { - val carrierMergedInfo = - mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } - val caps = - mock().also { - whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(carrierMergedInfo) - } - - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) - - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - - assertThat(latest).isTrue() - - job.cancel() - } - @Test fun mobileIsDefault_carrierMergedViaMobile_isDefault() = runBlocking(IMMEDIATE) { @@ -770,122 +748,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } - /** Regression test for b/272586234. */ - @Test - fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() = - runBlocking(IMMEDIATE) { - val carrierMergedInfo = - mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } - val caps = - mock().also { - whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) - } - - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) - - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - - assertThat(latest).isTrue() - - job.cancel() - } - - @Test - fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() = - runBlocking(IMMEDIATE) { - val carrierMergedInfo = - mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } - val caps = - mock().also { - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) - } - - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) - - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) - - assertThat(latest).isTrue() - - job.cancel() - } - - @Test - fun mobileIsDefault_isCarrierMergedViaUnderlyingWifi_isDefault() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) - - val underlyingNetwork = mock() - val carrierMergedInfo = - mock().apply { - whenever(this.isCarrierMerged).thenReturn(true) - } - val underlyingWifiCapabilities = - mock().also { - whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.transportInfo).thenReturn(carrierMergedInfo) - } - whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) - .thenReturn(underlyingWifiCapabilities) - - // WHEN the main capabilities have an underlying carrier merged network via WIFI - // transport and WifiInfo - val mainCapabilities = - mock().also { - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(null) - whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) - } - - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - - // THEN carrier merged is default, so mobile is default - assertThat(latest).isTrue() - - job.cancel() - } - - @Test - fun mobileIsDefault_isCarrierMergedViaUnderlyingCellular_isDefault() = - runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) - - val underlyingCarrierMergedNetwork = mock() - val carrierMergedInfo = - mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } - val underlyingCapabilities = - mock().also { - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) - } - whenever( - connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork) - ) - .thenReturn(underlyingCapabilities) - - // WHEN the main capabilities have an underlying carrier merged network via CELLULAR - // transport and VcnTransportInfo - val mainCapabilities = - mock().also { - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(it.transportInfo).thenReturn(null) - whenever(it.underlyingNetworks) - .thenReturn(listOf(underlyingCarrierMergedNetwork)) - } - - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) - - // THEN carrier merged is default, so mobile is default - assertThat(latest).isTrue() - - job.cancel() - } - @Test fun mobileIsDefault_wifiDefault_mobileNotDefault() = runBlocking(IMMEDIATE) { @@ -922,6 +784,195 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/272586234. */ + @Test + fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = + runBlocking(IMMEDIATE) { + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val caps = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = + runBlocking(IMMEDIATE) { + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val caps = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = + runBlocking(IMMEDIATE) { + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val caps = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = + runBlocking(IMMEDIATE) { + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val caps = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + val underlyingNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingWifiCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + yield() + + // THEN there's a carrier merged connection + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + val underlyingCarrierMergedNetwork = mock() + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + yield() + + // THEN there's a carrier merged connection + assertThat(latest).isTrue() + + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + // WHEN the default callback isn't carrier merged + val carrierMergedInfo = + mock().apply { whenever(this.isCarrierMerged).thenReturn(false) } + val caps = + mock().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() + + // BUT the wifi repo has gotten updates that it *is* carrier merged + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + yield() + + // THEN hasCarrierMergedConnection is true + assertThat(latest).isTrue() + + job.cancel() + } + @Test fun defaultConnectionIsValidated_startsAsFalse() { assertThat(underTest.defaultConnectionIsValidated.value).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 898e897703946..0ccf3f387f900 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -455,7 +455,50 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test - fun mobileIsDefault_usesRepoValue() = + fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + connectionsRepository.mobileIsDefault.value = false + connectionsRepository.hasCarrierMergedConnection.value = false + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + connectionsRepository.mobileIsDefault.value = true + connectionsRepository.hasCarrierMergedConnection.value = false + + assertThat(latest).isTrue() + + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + connectionsRepository.mobileIsDefault.value = false + connectionsRepository.hasCarrierMergedConnection.value = true + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun mobileIsDefault_updatesWhenRepoUpdates() = testScope.runTest { var latest: Boolean? = null val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) @@ -466,7 +509,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.mobileIsDefault.value = false assertThat(latest).isFalse() - connectionsRepository.mobileIsDefault.value = true + connectionsRepository.hasCarrierMergedConnection.value = true assertThat(latest).isTrue() job.cancel()