Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +9 −2 Original line number Diff line number Diff line Loading @@ -155,7 +155,8 @@ constructor( combine( unfilteredSubscriptions, mobileConnectionsRepo.activeMobileDataSubscriptionId, ) { unfilteredSubs, activeId -> connectivityRepository.vcnSubId, ) { unfilteredSubs, activeId, vcnSubId -> // Based on the old logic, if (unfilteredSubs.size != 2) { return@combine unfilteredSubs Loading @@ -182,7 +183,13 @@ constructor( // return the non-opportunistic info return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) } else { return@combine if (info1.subscriptionId == activeId) { // It's possible for the subId of the VCN to disagree with the active subId in // cases where the system has tried to switch but found no connection. In these // scenarios, VCN will always have the subId that we want to use, so use that // value instead of the activeId reported by telephony val subIdToKeep = vcnSubId ?: activeId return@combine if (info1.subscriptionId == subIdToKeep) { listOf(info1) } else { listOf(info2) Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +4 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,10 @@ constructor( model::messagePrinter, ) } fun logVcnSubscriptionId(subId: Int) { buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" }) } } private const val TAG = "ConnectivityInputLogger" packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +72 −37 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.ArrayRes import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable Loading @@ -50,10 +51,13 @@ import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Loading @@ -66,6 +70,16 @@ interface ConnectivityRepository { /** Observable for which connection(s) are currently default. */ val defaultConnections: StateFlow<DefaultConnectionModel> /** * Subscription ID of the [VcnTransportInfo] for the default connection. * * If the default network has a [VcnTransportInfo], then that transport info contains a subId of * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to * eventually catch up and reflect what is represented here in the VcnTransportInfo. */ val vcnSubId: StateFlow<Int?> } @SuppressLint("MissingPermission") Loading Loading @@ -118,16 +132,55 @@ constructor( initialValue = defaultHiddenIcons ) @SuppressLint("MissingPermission") override val defaultConnections: StateFlow<DefaultConnectionModel> = private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> = conflatedCallbackFlow { val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { override fun onLost(network: Network) { logger.logOnDefaultLost(network) trySend(null) } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) trySend(networkCapabilities) } } connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } .shareIn(scope, SharingStarted.WhileSubscribed()) override val vcnSubId: StateFlow<Int?> = defaultNetworkCapabilities .map { networkCapabilities -> networkCapabilities?.run { val subId = (transportInfo as? VcnTransportInfo)?.subId // Never return an INVALID_SUBSCRIPTION_ID (-1) if (subId != INVALID_SUBSCRIPTION_ID) { subId } else { null } } } .distinctUntilChanged() /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */ .onEach { logger.logVcnSubscriptionId(it ?: -2) } .stateIn(scope, SharingStarted.Eagerly, null) @SuppressLint("MissingPermission") override val defaultConnections: StateFlow<DefaultConnectionModel> = defaultNetworkCapabilities .map { networkCapabilities -> if (networkCapabilities == null) { // The system no longer has a default network, so everything is // non-default. trySend( DefaultConnectionModel( Wifi(isDefault = false), Mobile(isDefault = false), Loading @@ -135,30 +188,18 @@ constructor( Ethernet(isDefault = false), isValidated = false, ) ) } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) } else { val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) val isWifiDefault = networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) trySend( DefaultConnectionModel( Wifi(isWifiDefault), Mobile(isMobileDefault), Loading @@ -166,14 +207,8 @@ constructor( Ethernet(isEthernetDefault), isValidated, ) ) } } connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } .distinctUntilChanged() .onEach { logger.logDefaultConnectionsChanged(it) } .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel()) Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +46 −0 Original line number Diff line number Diff line Loading @@ -271,6 +271,52 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } @Test fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = testScope.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(true, true), grouped = true, ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectivityRepository.vcnSubId.value = SUB_3_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) var latest: List<SubscriptionModel>? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(listOf(sub3)) job.cancel() } @Test fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = testScope.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(true, true), grouped = true, ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectivityRepository.vcnSubId.value = SUB_1_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) var latest: List<SubscriptionModel>? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(listOf(sub1)) job.cancel() } @Test fun activeDataConnection_turnedOn() = testScope.runTest { Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +138 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager Loading @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi Loading Loading @@ -654,6 +656,139 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { job.cancel() } @Test fun vcnSubId_initiallyNull() { assertThat(underTest.vcnSubId.value).isNull() } @Test fun vcnSubId_tracksVcnTransportInfo() = testScope.runTest { val vcnInfo = VcnTransportInfo(SUB_1_ID) var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) job.cancel() } @Test fun vcnSubId_filersOutInvalid() = testScope.runTest { val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID) var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_nullIfNoTransportInfo() = testScope.runTest { var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(null) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_nullIfVcnInfoIsNotCellular() = testScope.runTest { // If the underlying network of the VCN is a WiFi network, then there is no subId that // could disagree with telephony's active data subscription id. var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val wifiInfo = mock<WifiInfo>() val vcnInfo = VcnTransportInfo(wifiInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_changingVcnInfoIsTracked() = testScope.runTest { var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val wifiInfo = mock<WifiInfo>() val wifiVcnInfo = VcnTransportInfo(wifiInfo) val sub1VcnInfo = VcnTransportInfo(SUB_1_ID) val sub2VcnInfo = VcnTransportInfo(SUB_2_ID) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(wifiVcnInfo) } // WIFI VCN info getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() // Cellular VCN info with subId 1 whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true) whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) // Cellular VCN info with subId 2 whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_2_ID) // No VCN anymore whenever(capabilities.transportInfo).thenReturn(null) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { val wifiInfo = mock<WifiInfo>() Loading Loading @@ -964,6 +1099,9 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { private const val SLOT_WIFI = "wifi" private const val SLOT_MOBILE = "mobile" private const val SUB_1_ID = 1 private const val SUB_2_ID = 2 const val NETWORK_ID = 45 val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } } Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +9 −2 Original line number Diff line number Diff line Loading @@ -155,7 +155,8 @@ constructor( combine( unfilteredSubscriptions, mobileConnectionsRepo.activeMobileDataSubscriptionId, ) { unfilteredSubs, activeId -> connectivityRepository.vcnSubId, ) { unfilteredSubs, activeId, vcnSubId -> // Based on the old logic, if (unfilteredSubs.size != 2) { return@combine unfilteredSubs Loading @@ -182,7 +183,13 @@ constructor( // return the non-opportunistic info return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) } else { return@combine if (info1.subscriptionId == activeId) { // It's possible for the subId of the VCN to disagree with the active subId in // cases where the system has tried to switch but found no connection. In these // scenarios, VCN will always have the subId that we want to use, so use that // value instead of the activeId reported by telephony val subIdToKeep = vcnSubId ?: activeId return@combine if (info1.subscriptionId == subIdToKeep) { listOf(info1) } else { listOf(info2) Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +4 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,10 @@ constructor( model::messagePrinter, ) } fun logVcnSubscriptionId(subId: Int) { buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" }) } } private const val TAG = "ConnectivityInputLogger"
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +72 −37 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.annotation.ArrayRes import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable Loading @@ -50,10 +51,13 @@ import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Loading @@ -66,6 +70,16 @@ interface ConnectivityRepository { /** Observable for which connection(s) are currently default. */ val defaultConnections: StateFlow<DefaultConnectionModel> /** * Subscription ID of the [VcnTransportInfo] for the default connection. * * If the default network has a [VcnTransportInfo], then that transport info contains a subId of * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to * eventually catch up and reflect what is represented here in the VcnTransportInfo. */ val vcnSubId: StateFlow<Int?> } @SuppressLint("MissingPermission") Loading Loading @@ -118,16 +132,55 @@ constructor( initialValue = defaultHiddenIcons ) @SuppressLint("MissingPermission") override val defaultConnections: StateFlow<DefaultConnectionModel> = private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> = conflatedCallbackFlow { val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { override fun onLost(network: Network) { logger.logOnDefaultLost(network) trySend(null) } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) trySend(networkCapabilities) } } connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } .shareIn(scope, SharingStarted.WhileSubscribed()) override val vcnSubId: StateFlow<Int?> = defaultNetworkCapabilities .map { networkCapabilities -> networkCapabilities?.run { val subId = (transportInfo as? VcnTransportInfo)?.subId // Never return an INVALID_SUBSCRIPTION_ID (-1) if (subId != INVALID_SUBSCRIPTION_ID) { subId } else { null } } } .distinctUntilChanged() /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */ .onEach { logger.logVcnSubscriptionId(it ?: -2) } .stateIn(scope, SharingStarted.Eagerly, null) @SuppressLint("MissingPermission") override val defaultConnections: StateFlow<DefaultConnectionModel> = defaultNetworkCapabilities .map { networkCapabilities -> if (networkCapabilities == null) { // The system no longer has a default network, so everything is // non-default. trySend( DefaultConnectionModel( Wifi(isDefault = false), Mobile(isDefault = false), Loading @@ -135,30 +188,18 @@ constructor( Ethernet(isDefault = false), isValidated = false, ) ) } override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) } else { val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) val isWifiDefault = networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) trySend( DefaultConnectionModel( Wifi(isWifiDefault), Mobile(isMobileDefault), Loading @@ -166,14 +207,8 @@ constructor( Ethernet(isEthernetDefault), isValidated, ) ) } } connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } .distinctUntilChanged() .onEach { logger.logDefaultConnectionsChanged(it) } .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel()) Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +46 −0 Original line number Diff line number Diff line Loading @@ -271,6 +271,52 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } @Test fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = testScope.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(true, true), grouped = true, ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectivityRepository.vcnSubId.value = SUB_3_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) var latest: List<SubscriptionModel>? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(listOf(sub3)) job.cancel() } @Test fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = testScope.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(true, true), grouped = true, ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectivityRepository.vcnSubId.value = SUB_1_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) var latest: List<SubscriptionModel>? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(listOf(sub1)) job.cancel() } @Test fun activeDataConnection_turnedOn() = testScope.runTest { Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +138 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager Loading @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi Loading Loading @@ -654,6 +656,139 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { job.cancel() } @Test fun vcnSubId_initiallyNull() { assertThat(underTest.vcnSubId.value).isNull() } @Test fun vcnSubId_tracksVcnTransportInfo() = testScope.runTest { val vcnInfo = VcnTransportInfo(SUB_1_ID) var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) job.cancel() } @Test fun vcnSubId_filersOutInvalid() = testScope.runTest { val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID) var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_nullIfNoTransportInfo() = testScope.runTest { var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(null) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_nullIfVcnInfoIsNotCellular() = testScope.runTest { // If the underlying network of the VCN is a WiFi network, then there is no subId that // could disagree with telephony's active data subscription id. var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val wifiInfo = mock<WifiInfo>() val vcnInfo = VcnTransportInfo(wifiInfo) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) whenever(it.transportInfo).thenReturn(vcnInfo) } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun vcnSubId_changingVcnInfoIsTracked() = testScope.runTest { var latest: Int? = null val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this) val wifiInfo = mock<WifiInfo>() val wifiVcnInfo = VcnTransportInfo(wifiInfo) val sub1VcnInfo = VcnTransportInfo(SUB_1_ID) val sub2VcnInfo = VcnTransportInfo(SUB_2_ID) val capabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) whenever(it.transportInfo).thenReturn(wifiVcnInfo) } // WIFI VCN info getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() // Cellular VCN info with subId 1 whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true) whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_1_ID) // Cellular VCN info with subId 2 whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isEqualTo(SUB_2_ID) // No VCN anymore whenever(capabilities.transportInfo).thenReturn(null) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) assertThat(latest).isNull() job.cancel() } @Test fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { val wifiInfo = mock<WifiInfo>() Loading Loading @@ -964,6 +1099,9 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { private const val SLOT_WIFI = "wifi" private const val SLOT_MOBILE = "mobile" private const val SUB_1_ID = 1 private const val SUB_2_ID = 2 const val NETWORK_ID = 45 val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } } Loading