Merge "Prefer the VCN subscription when filtering for CBRS" into udc-dev

This commit is contained in:
Evan Laird
2023-05-08 13:38:17 +00:00
committed by Android (Google) Code Review
6 changed files with 271 additions and 39 deletions

View File

@@ -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
@@ -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)

View File

@@ -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"

View File

@@ -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
@@ -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
/**
@@ -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")
@@ -118,24 +132,13 @@ 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)
// The system no longer has a default network, so everything is
// non-default.
trySend(
DefaultConnectionModel(
Wifi(isDefault = false),
Mobile(isDefault = false),
CarrierMerged(isDefault = false),
Ethernet(isDefault = false),
isValidated = false,
)
)
trySend(null)
}
override fun onCapabilitiesChanged(
@@ -143,30 +146,7 @@ constructor(
networkCapabilities: NetworkCapabilities,
) {
logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
val wifiInfo =
networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
val isWifiDefault =
networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
val isMobileDefault =
networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
val isEthernetDefault =
networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
val isValidated =
networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
trySend(
DefaultConnectionModel(
Wifi(isWifiDefault),
Mobile(isMobileDefault),
CarrierMerged(isCarrierMergedDefault),
Ethernet(isEthernetDefault),
isValidated,
)
)
trySend(networkCapabilities)
}
}
@@ -174,6 +154,61 @@ constructor(
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.
DefaultConnectionModel(
Wifi(isDefault = false),
Mobile(isDefault = false),
CarrierMerged(isDefault = false),
Ethernet(isDefault = false),
isValidated = false,
)
} else {
val wifiInfo =
networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
val isWifiDefault =
networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
DefaultConnectionModel(
Wifi(isWifiDefault),
Mobile(isMobileDefault),
CarrierMerged(isCarrierMergedDefault),
Ethernet(isEthernetDefault),
isValidated,
)
}
}
.distinctUntilChanged()
.onEach { logger.logDefaultConnectionsChanged(it) }
.stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())

View File

@@ -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 {

View File

@@ -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
@@ -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
@@ -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>()
@@ -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) }
}

View File

@@ -30,6 +30,8 @@ class FakeConnectivityRepository : ConnectivityRepository {
override val defaultConnections: StateFlow<DefaultConnectionModel> =
MutableStateFlow(DefaultConnectionModel())
override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
_forceHiddenIcons.value = hiddenIcons
}