Skip to content
Snippets Groups Projects
Commit 502ec035 authored by Evan Laird's avatar Evan Laird
Browse files

[Sb] Add prioritize_latency tracking in mobile connections

This CL adds a network callback -- registered via ConnectivityManager --
for every mobile subscription that we know about. This amounts to one
listeners per subId (more on that below). The network callback is
registered only to receive information about _that_ subId's network
pertaining to the NET_CAPABILITY_PRIORITIZE_LATENCY. We'll use this flag
downstream to determine whether or not to show a network slice
attribution.

On ConnectivityManager's callback list:
  The docs on ConnectivityManagerService claims that we should be careful
about too many callbacks registered with the system. If we need to get
rid of the 1:1 mapping from subscription to registrant, then we would
have to do some work to register a _single_ callback that maintained a
map of <netId, networkCapabilities>. It would also presumably have to be
eagerly listening, and make sure to update the map whenever a network
disappears
  Note that this doesn't actually give us a huge performance benefit
from what I can tell. This is because our single registrant would still
get a callback-per-network in which the capabilities changed. Therefore,
we still get the penalty of N incoming IPCs when there are N networks
that meet the criteria. So we only should worry about that in the case
where we run close to the limit of 100 subscribers per process.

Test: tests in statusbar/pipeline
Bug: 270385675
Change-Id: If42b52ad6e14bdc258c90761e3e6dd629bbe9d3d
parent 171f2e42
No related branches found
No related tags found
No related merge requests found
Showing
with 167 additions and 1 deletion
......@@ -190,6 +190,24 @@ constructor(
fun logOnSimStateChanged() {
buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
}
fun logPrioritizedNetworkAvailable(netId: Int) {
buffer.log(
TAG,
LogLevel.INFO,
{ int1 = netId },
{ "Found prioritized network (nedId=$int1)" },
)
}
fun logPrioritizedNetworkLost(netId: Int) {
buffer.log(
TAG,
LogLevel.INFO,
{ int1 = netId },
{ "Lost prioritized network (nedId=$int1)" },
)
}
}
private const val TAG = "MobileInputLog"
......@@ -131,6 +131,12 @@ interface MobileConnectionRepository {
*/
val isAllowedDuringAirplaneMode: StateFlow<Boolean>
/**
* True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
* network slice
*/
val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
companion object {
/** The default number of levels to use for [numberOfLevels]. */
const val DEFAULT_NUM_LEVELS = 4
......
......@@ -191,6 +191,8 @@ class DemoMobileConnectionRepository(
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
/**
* Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
* from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
......@@ -225,6 +227,7 @@ class DemoMobileConnectionRepository(
_resolvedNetworkType.value = resolvedNetworkType
isAllowedDuringAirplaneMode.value = false
hasPrioritizedNetworkCapabilities.value = event.slice
}
fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
......@@ -250,6 +253,7 @@ class DemoMobileConnectionRepository(
_isGsm.value = false
_carrierNetworkChangeActive.value = false
isAllowedDuringAirplaneMode.value = true
hasPrioritizedNetworkCapabilities.value = false
}
companion object {
......
......@@ -76,6 +76,7 @@ constructor(
val carrierNetworkChange = getString("carriernetworkchange") == "show"
val roaming = getString("roam") == "show"
val name = getString("networkname") ?: "demo mode"
val slice = getString("slice").toBoolean()
return Mobile(
level = level,
......@@ -87,6 +88,7 @@ constructor(
carrierNetworkChange = carrierNetworkChange,
roaming = roaming,
name = name,
slice = slice,
)
}
}
......
......@@ -36,6 +36,7 @@ sealed interface FakeNetworkEventModel {
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
val slice: Boolean = false,
) : FakeNetworkEventModel
data class MobileDisabled(
......
......@@ -174,6 +174,13 @@ class CarrierMergedConnectionRepository(
*/
override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
/**
* It's not currently considered possible that a carrier merged network can have these
* prioritized capabilities. If we need to track them, we can add the same check as is in
* [MobileConnectionRepositoryImpl].
*/
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
companion object {
......
......@@ -309,6 +309,15 @@ class FullMobileConnectionRepository(
activeRepo.value.isAllowedDuringAirplaneMode.value,
)
override val hasPrioritizedNetworkCapabilities =
activeRepo
.flatMapLatest { it.hasPrioritizedNetworkCapabilities }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
activeRepo.value.hasPrioritizedNetworkCapabilities.value,
)
class Factory
@Inject
constructor(
......
......@@ -21,6 +21,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
......@@ -91,6 +96,7 @@ class MobileConnectionRepositoryImpl(
subscriptionModel: StateFlow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
systemUiCarrierConfig: SystemUiCarrierConfig,
broadcastDispatcher: BroadcastDispatcher,
......@@ -374,11 +380,50 @@ class MobileConnectionRepositoryImpl(
/** Typical mobile connections aren't available during airplane mode. */
override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
/**
* Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
* we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
* these capabilities that are used here must also be represented in the
* self_certified_network_capabilities.xml config file
*/
@SuppressLint("WrongConstant")
private val networkSliceRequest =
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
.setSubscriptionIds(setOf(subId))
.build()
@SuppressLint("MissingPermission")
override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> =
conflatedCallbackFlow {
// Our network callback listens only for this.subId && net_cap_prioritize_latency
// therefore our state is a simple mapping of whether or not that network exists
val callback =
object : NetworkCallback() {
override fun onAvailable(network: Network) {
logger.logPrioritizedNetworkAvailable(network.netId)
trySend(true)
}
override fun onLost(network: Network) {
logger.logPrioritizedNetworkLost(network.netId)
trySend(false)
}
}
connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
class Factory
@Inject
constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
private val carrierConfigRepository: CarrierConfigRepository,
......@@ -399,6 +444,7 @@ class MobileConnectionRepositoryImpl(
subscriptionModel,
defaultNetworkName,
networkNameSeparator,
connectivityManager,
telephonyManager.createForSubscriptionId(subId),
carrierConfigRepository.getOrCreateConfigForSubId(subId),
broadcastDispatcher,
......@@ -421,11 +467,17 @@ private fun Intent.carrierId(): Int =
*/
sealed interface CallbackEvent {
data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
data class OnDataActivity(val direction: Int) : CallbackEvent
data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
}
......
......@@ -60,6 +60,8 @@ class FakeMobileConnectionRepository(
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
......
......@@ -123,6 +123,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange = testCase.carrierNetworkChange,
roaming = testCase.roaming,
name = "demo name",
slice = testCase.slice,
)
fakeNetworkEventFlow.value = networkModel
......@@ -142,6 +143,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
......@@ -165,6 +167,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387): check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
......@@ -190,6 +193,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
val slice: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
......@@ -200,7 +204,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
"activity=$activity, " +
"carrierNetworkChange=$carrierNetworkChange, " +
"roaming=$roaming, " +
"name=$name)"
"name=$name," +
"slice=$slice)"
}
// Convenience for iterating test data and creating new cases
......@@ -214,6 +219,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange: Boolean? = null,
roaming: Boolean? = null,
name: String? = null,
slice: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
......@@ -225,6 +231,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
roaming = roaming ?: this.roaming,
name = name ?: this.name,
slice = slice ?: this.slice,
)
}
......@@ -254,6 +261,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
// false first so the base case doesn't have roaming set (more common)
private val roaming = listOf(false, true)
private val names = listOf("name 1", "name 2")
private val slice = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
......@@ -291,6 +299,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange.first(),
roaming.first(),
names.first(),
slice.first(),
)
val tail =
......@@ -303,6 +312,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
roaming.map { baseCase.modifiedBy(roaming = it) },
names.map { baseCase.modifiedBy(name = it) },
slice.map { baseCase.modifiedBy(slice = it) }
)
.flatten()
......
......@@ -549,6 +549,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
......@@ -574,6 +575,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387) check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
......@@ -599,6 +601,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(conn.isEmergencyOnly.value).isFalse()
assertThat(conn.isGsm.value).isFalse()
assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()
job.cancel()
}
}
......
......@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
......@@ -80,6 +81,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
private val connectivityManager = mock<ConnectivityManager>()
private val subscriptionModel =
MutableStateFlow(
......@@ -678,6 +680,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
connectivityManager,
telephonyManager,
systemUiCarrierConfig = mock(),
fakeBroadcastDispatcher,
......
......@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
......@@ -85,7 +87,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
......@@ -107,6 +111,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
......@@ -144,6 +149,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
......@@ -904,6 +910,45 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isFalse()
}
@Test
fun hasPrioritizedCaps_defaultFalse() {
assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse()
}
@Test
fun hasPrioritizedCaps_trueWhenAvailable() =
testScope.runTest {
val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
val callback: NetworkCallback =
withArgCaptor<NetworkCallback> {
verify(connectivityManager).registerNetworkCallback(any(), capture())
}
callback.onAvailable(mock())
assertThat(latest).isTrue()
}
@Test
fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() =
testScope.runTest {
val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
val callback: NetworkCallback =
withArgCaptor<NetworkCallback> {
verify(connectivityManager).registerNetworkCallback(any(), capture())
}
callback.onAvailable(mock())
assertThat(latest).isTrue()
callback.onLost(mock())
assertThat(latest).isFalse()
}
private inline fun <reified T> getTelephonyCallbackForType(): T {
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
......
......@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.CarrierNetworkListener
......@@ -96,6 +97,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
......@@ -129,6 +131,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME,
SEP,
connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
......
......@@ -180,6 +180,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
MobileConnectionRepositoryImpl.Factory(
context,
fakeBroadcastDispatcher,
connectivityManager,
telephonyManager = telephonyManager,
bgDispatcher = dispatcher,
logger = logger,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment