From ca5ed2ceb0be561cc6b55c230199c2fd9a10f4f3 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Tue, 28 Mar 2023 18:33:15 +0000 Subject: [PATCH] [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) {