Merge "Prefer the VCN subscription when filtering for CBRS" into udc-dev am: 541ef6fb00

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22889527

Change-Id: I386a63dc375d58af9a07347ebae7b44d3019e100
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Evan Laird
2023-05-08 13:48:07 +00:00
committed by Automerger Merge Worker
6 changed files with 271 additions and 39 deletions

View File

@@ -155,7 +155,8 @@ constructor(
combine( combine(
unfilteredSubscriptions, unfilteredSubscriptions,
mobileConnectionsRepo.activeMobileDataSubscriptionId, mobileConnectionsRepo.activeMobileDataSubscriptionId,
) { unfilteredSubs, activeId -> connectivityRepository.vcnSubId,
) { unfilteredSubs, activeId, vcnSubId ->
// Based on the old logic, // Based on the old logic,
if (unfilteredSubs.size != 2) { if (unfilteredSubs.size != 2) {
return@combine unfilteredSubs return@combine unfilteredSubs
@@ -182,7 +183,13 @@ constructor(
// return the non-opportunistic info // return the non-opportunistic info
return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
} else { } 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) listOf(info1)
} else { } else {
listOf(info2) listOf(info2)

View File

@@ -61,6 +61,10 @@ constructor(
model::messagePrinter, model::messagePrinter,
) )
} }
fun logVcnSubscriptionId(subId: Int) {
buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" })
}
} }
private const val TAG = "ConnectivityInputLogger" 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.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo import android.net.wifi.WifiInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
@@ -50,10 +51,13 @@ import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
/** /**
@@ -66,6 +70,16 @@ interface ConnectivityRepository {
/** Observable for which connection(s) are currently default. */ /** Observable for which connection(s) are currently default. */
val defaultConnections: StateFlow<DefaultConnectionModel> 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") @SuppressLint("MissingPermission")
@@ -118,24 +132,13 @@ constructor(
initialValue = defaultHiddenIcons initialValue = defaultHiddenIcons
) )
@SuppressLint("MissingPermission") private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> =
override val defaultConnections: StateFlow<DefaultConnectionModel> =
conflatedCallbackFlow { conflatedCallbackFlow {
val callback = val callback =
object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) { override fun onLost(network: Network) {
logger.logOnDefaultLost(network) logger.logOnDefaultLost(network)
// The system no longer has a default network, so everything is trySend(null)
// non-default.
trySend(
DefaultConnectionModel(
Wifi(isDefault = false),
Mobile(isDefault = false),
CarrierMerged(isDefault = false),
Ethernet(isDefault = false),
isValidated = false,
)
)
} }
override fun onCapabilitiesChanged( override fun onCapabilitiesChanged(
@@ -143,30 +146,7 @@ constructor(
networkCapabilities: NetworkCapabilities, networkCapabilities: NetworkCapabilities,
) { ) {
logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
trySend(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,
)
)
} }
} }
@@ -174,6 +154,61 @@ constructor(
awaitClose { connectivityManager.unregisterNetworkCallback(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.
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() .distinctUntilChanged()
.onEach { logger.logDefaultConnectionsChanged(it) } .onEach { logger.logDefaultConnectionsChanged(it) }
.stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel()) .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())

View File

@@ -271,6 +271,52 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel() 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 @Test
fun activeDataConnection_turnedOn() = fun activeDataConnection_turnedOn() =
testScope.runTest { testScope.runTest {

View File

@@ -24,6 +24,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo import android.net.wifi.WifiInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager 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.tuner.TunerService
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -654,6 +656,139 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
job.cancel() 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 @Test
fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
val wifiInfo = mock<WifiInfo>() val wifiInfo = mock<WifiInfo>()
@@ -964,6 +1099,9 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
private const val SLOT_WIFI = "wifi" private const val SLOT_WIFI = "wifi"
private const val SLOT_MOBILE = "mobile" 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 const val NETWORK_ID = 45
val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } 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> = override val defaultConnections: StateFlow<DefaultConnectionModel> =
MutableStateFlow(DefaultConnectionModel()) MutableStateFlow(DefaultConnectionModel())
override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) { fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
_forceHiddenIcons.value = hiddenIcons _forceHiddenIcons.value = hiddenIcons
} }