Merge "Implement network avoid bad wifi from carrier config" into main

This commit is contained in:
Jimi Chen 2025-05-08 17:46:53 -07:00 • committed by Android (Google) Code Review
commit ae13aeb0fd
2 changed files with 493 additions and 47 deletions

View file

@ -17,32 +17,40 @@
package com.android.server.connectivity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
import android.net.platform.flags.Flags.FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG
import android.os.Build
import android.os.Handler
import android.os.test.TestLooper
import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.test.mock.MockContentResolver
import androidx.test.filters.SmallTest
import com.android.connectivity.resources.R
import com.android.internal.os.BackgroundThread
import com.android.internal.util.test.FakeSettingsProvider
import com.android.modules.utils.build.SdkLevel
import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.com.android.testutils.SetFeatureFlagsRule
import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag
import com.android.testutils.postAndWait
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@ -66,56 +74,156 @@ const val HANDLER_TIMEOUT_MS = 400
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class MultinetworkPolicyTrackerTest {
// This wrapper class prevents JUnit from attempting to load unsupported system classes
// that are present in the System Test (S/T) image, which would otherwise cause test failures.
private class CarrierConfigChangeRunner(
val csLooper: TestLooper,
val bgHandler: Handler,
val listener: CarrierConfigManager.CarrierConfigChangeListener
) {
fun runCarrierConfigChangeOnBackgroundThread(subId: Int) {
bgHandler.postAndWait {
listener.onCarrierConfigChanged(
0, /* logicalSlotIndex */
subId, /* subscriptionId */
0, /* carrierId */
0 /* specificCarrierId */
)
}
csLooper.dispatchNext()
}
}
private val featureFlags = HashSet<String>()
// This will set feature flags from @FeatureFlag annotations
// into the map before setUp() runs.
@get:Rule
val setFeatureFlagsRule = SetFeatureFlagsRule(
{ name, enabled ->
if (enabled == true) featureFlags.add(name) else featureFlags.remove(name) },
{ name -> featureFlags.contains(name) }
)
@get:Rule
val ignoreRule = DevSdkIgnoreRule()
private val resources = mock(Resources::class.java).also {
doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
doReturn(0).`when`(it).getInteger(R.integer.config_activelyPreferBadWifi)
}
private val telephonyManager = mock(TelephonyManager::class.java)
private val carrierConfigManager = mock(CarrierConfigManager::class.java)
private val subscriptionManager = mock(SubscriptionManager::class.java).also {
doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt())
}
private val packageManager = mock(PackageManager::class.java).also {
doReturn(true).`when`(it)
.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
}
private val resolver = MockContentResolver().apply {
addProvider(Settings.AUTHORITY, FakeSettingsProvider()) }
private val context = mock(Context::class.java).also {
doReturn(Context.TELEPHONY_SERVICE).`when`(it)
.getSystemServiceName(TelephonyManager::class.java)
.getSystemServiceName(TelephonyManager::class.java)
doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE)
if (it.getSystemService(TelephonyManager::class.java) == null) {
// Test is using mockito extended
doCallRealMethod().`when`(it).getSystemService(TelephonyManager::class.java)
}
doReturn(subscriptionManager).`when`(it)
.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
doReturn(carrierConfigManager).`when`(it)
.getSystemService(CarrierConfigManager::class.java)
doReturn(resolver).`when`(it).contentResolver
doReturn(resources).`when`(it).resources
doReturn(it).`when`(it).createConfigurationContext(any())
doReturn(packageManager).`when`(it).packageManager
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
ConnectivityResources.setResourcesContextForTest(it)
}
private val csLooper = TestLooper()
private val handler = Handler(csLooper.looper)
private val trackerDependencies = MultinetworkPolicyTrackerTestDependencies(resources)
private val tracker = MultinetworkPolicyTracker(context, handler,
null /* avoidBadWifiCallback */, trackerDependencies)
private val bgHandler = BackgroundThread.getHandler()
private val trackerDependencies =
MultinetworkPolicyTrackerTestDependencies(resources)
// this needs to be initialized in setUp() because
// the featureFlag from the annotation is not ready here
private lateinit var tracker: MultinetworkPolicyTracker
private fun assertMultipathPreference(preference: Int) {
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
preference.toString())
Settings.Global.putString(
resolver,
NETWORK_METERED_MULTIPATH_PREFERENCE,
preference.toString()
)
tracker.updateMeteredMultipathPreference()
assertEquals(preference, tracker.meteredMultipathPreference)
}
private fun runOnActiveDataSubscriptionIdChanged() {
val testSubId = 1000
val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */,
"TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */,
"123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */,
""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */,
"1"/* cardString */)
doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId)
// Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in
// MultinetworkPolicyTracker should be also updated after subId changed.
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
Settings.Global.putString(
resolver,
NETWORK_METERED_MULTIPATH_PREFERENCE,
MULTIPATH_PREFERENCE_PERFORMANCE.toString()
)
assertTrue(tracker.avoidBadWifi)
val listenerCaptor = ArgumentCaptor.forClass(
ActiveDataSubscriptionIdListener::class.java
)
verify(telephonyManager, times(1))
.registerTelephonyCallback(any(), listenerCaptor.capture())
val listener = listenerCaptor.value
csLooper.dispatchAll()
// Mock the carrier configuration to return false
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(testSubId, false)
listener.onActiveDataSubscriptionIdChanged(testSubId)
// Check if avoidBadWifi and meteredMultipathPreference values have been updated.
assertFalse(tracker.avoidBadWifi)
assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
}
@Before
fun setUp() {
trackerDependencies.setAvoidBadWifiFromCarrierConfigFeature(
featureFlags.contains(FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG)
)
trackerDependencies.setBackgroundThreadHandler(bgHandler)
tracker = MultinetworkPolicyTracker(
context,
handler,
null /* avoidBadWifiCallback */,
trackerDependencies
)
tracker.start()
}
@After
fun tearDown() {
ConnectivityResources.setResourcesContextForTest(null)
trackerDependencies.resetAvoidBadWifiCarrierConfigForSubIdMap()
}
@Test
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, false)
fun testUpdateMeteredMultipathPreference() {
assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER)
assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY)
@ -123,6 +231,7 @@ class MultinetworkPolicyTrackerTest {
}
@Test
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, false)
fun testUpdateAvoidBadWifi() {
doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
@ -165,32 +274,119 @@ class MultinetworkPolicyTrackerTest {
}
@Test
fun testOnActiveDataSubscriptionIdChanged() {
val testSubId = 1000
val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */,
"TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */,
"123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */,
""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */,
"1"/* cardString */)
doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId)
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, false)
fun testOnActiveDataSubscriptionIdChangedFromResources() {
runOnActiveDataSubscriptionIdChanged()
}
// Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in
// MultinetworkPolicyTracker should be also updated after subId changed.
@Test
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, true)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.BAKLAVA)
fun testOnActiveDataSubscriptionIdChangedFromCarrierConfig() {
runOnActiveDataSubscriptionIdChanged()
}
@Test
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, true)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.BAKLAVA)
fun testAvoidBadWifiWithGlobalSetting() {
val activeSubId = 1000
// Mock the initial global setting to false
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
MULTIPATH_PREFERENCE_PERFORMANCE.toString())
assertTrue(tracker.avoidBadWifi)
// Mock the initial carrier configuration to return true
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(activeSubId, true)
val listenerCaptor = ArgumentCaptor.forClass(
ActiveDataSubscriptionIdListener::class.java)
ActiveDataSubscriptionIdListener::class.java
)
verify(telephonyManager, times(1))
.registerTelephonyCallback(any(), listenerCaptor.capture())
.registerTelephonyCallback(any(), listenerCaptor.capture())
val listener = listenerCaptor.value
listener.onActiveDataSubscriptionIdChanged(testSubId)
csLooper.dispatchAll()
// Check if avoidBadWifi and meteredMultipathPreference values have been updated.
// Simulate a change in the active data subscription ID to activeSubId.
listener.onActiveDataSubscriptionIdChanged(activeSubId)
// Assert that the tracker's avoidBadWifi flag is false based on global setting
assertFalse(tracker.avoidBadWifi)
assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
assertTrue(tracker.activelyPreferBadWifi)
// Mock the initial global setting to true
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
// Mock the initial carrier configuration to return false
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(activeSubId, false)
// Assert that the tracker's avoidBadWifi flag is true based on global setting
assertTrue(tracker.updateAvoidBadWifi())
assertTrue(tracker.avoidBadWifi)
assertTrue(tracker.activelyPreferBadWifi)
}
@Test
@FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, true)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.BAKLAVA)
fun testUpdateAvoidBadWifiOnCarrierConfigChange() {
// Mock the initial global setting to null
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, null)
val activeSubId = 1000
val listenerCaptor = ArgumentCaptor.forClass(
ActiveDataSubscriptionIdListener::class.java
)
verify(telephonyManager, times(1))
.registerTelephonyCallback(any(), listenerCaptor.capture())
val listener = listenerCaptor.value
// Simulate a change in the active data subscription ID to activeSubId.
listener.onActiveDataSubscriptionIdChanged(activeSubId)
val carrierConfiglistenCaptor = ArgumentCaptor.forClass(
CarrierConfigManager.CarrierConfigChangeListener::class.java
)
// Mock the initial carrier configuration to return false
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(activeSubId, false)
verify(carrierConfigManager, times(1))
.registerCarrierConfigChangeListener(any(), carrierConfiglistenCaptor.capture())
val carrierConfiglistener = carrierConfiglistenCaptor.value
// dispatch for the first carrier config initialization on the handler thread
csLooper.dispatchAll()
val runner = CarrierConfigChangeRunner(csLooper, bgHandler, carrierConfiglistener)
// Simulate a carrier configuration change for the initial activeSubId (1000),
// with avoid bad Wi-Fi set to false.
runner.runCarrierConfigChangeOnBackgroundThread(activeSubId)
// Assert that the tracker's avoidBadWifi flag is false after the carrier config change.
assertFalse(tracker.avoidBadWifi)
assertTrue(tracker.activelyPreferBadWifi)
// Mock the carrier configuration to now return true for KEY_AVOID_BAD_WIFI_BOOL.
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(activeSubId, true)
// Simulate another carrier configuration change for the same activeSubId (1000),
// now with avoid bad Wi-Fi set to true.
runner.runCarrierConfigChangeOnBackgroundThread(activeSubId)
// Assert that the tracker's avoidBadWifi flag is now true.
assertTrue(tracker.avoidBadWifi)
assertTrue(tracker.activelyPreferBadWifi)
val changedActiveSubId = 1001
// Mock the carrier configuration to return false
trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(changedActiveSubId, false)
// Simulate a carrier configuration change for a different subscription ID.
runner.runCarrierConfigChangeOnBackgroundThread(changedActiveSubId)
// Assert that the tracker's avoidBadWifi flag remains true,
// because the config change was for a non-active subscription.
assertTrue(tracker.avoidBadWifi)
assertTrue(tracker.activelyPreferBadWifi)
// Simulate a change in the active data subscription ID to the new ID (1001).
listener.onActiveDataSubscriptionIdChanged(changedActiveSubId)
// Assert that the tracker's avoidBadWifi flag is now false,
// reflecting the carrier config of the new active subscription.
assertFalse(tracker.avoidBadWifi)
assertTrue(tracker.activelyPreferBadWifi)
}
}

View file

@ -1,33 +1,60 @@
package com.android.server.connectivity
import android.content.Context
import android.content.res.Resources
import android.os.Handler
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
import android.provider.DeviceConfig.OnPropertiesChangedListener
import com.android.internal.annotations.GuardedBy
import com.android.internal.os.BackgroundThread
import com.android.server.connectivity.MultinetworkPolicyTracker.CONFIG_ACTIVELY_PREFER_BAD_WIFI
import java.util.concurrent.Executor
class MultinetworkPolicyTrackerTestDependencies(private val resources: Resources) :
MultinetworkPolicyTracker.Dependencies() {
class MultinetworkPolicyTrackerTestDependencies(
private val resources: Resources
) : MultinetworkPolicyTracker.Dependencies() {
@GuardedBy("listeners")
private var configActivelyPreferBadWifi = 0
// TODO : move this to an actual fake device config object
@GuardedBy("listeners")
private val listeners = mutableListOf<Pair<Executor, OnPropertiesChangedListener>>()
private var avoidBadWifiFromCarrierConfigFeature = false
private val avoidBadWifiCarrierConfigForSubIdMap = HashMap<Int, Boolean>()
private var backgroundThreadHandler = BackgroundThread.getHandler()
fun putConfigActivelyPreferBadWifi(value: Int) {
synchronized(listeners) {
if (value == configActivelyPreferBadWifi) return
configActivelyPreferBadWifi = value
val p = DeviceConfig.Properties(NAMESPACE_CONNECTIVITY,
mapOf(CONFIG_ACTIVELY_PREFER_BAD_WIFI to value.toString()))
val p = DeviceConfig.Properties(
NAMESPACE_CONNECTIVITY,
mapOf(CONFIG_ACTIVELY_PREFER_BAD_WIFI to value.toString())
)
listeners.forEach { (executor, listener) ->
executor.execute { listener.onPropertiesChanged(p) }
}
}
}
fun setAvoidBadWifiFromCarrierConfigFeature(value: Boolean) {
avoidBadWifiFromCarrierConfigFeature = value
}
fun setAvoidBadWifiCarrierConfigForSubId(subId: Int, value: Boolean) {
avoidBadWifiCarrierConfigForSubIdMap[subId] = value
}
fun resetAvoidBadWifiCarrierConfigForSubIdMap() {
avoidBadWifiCarrierConfigForSubIdMap.clear()
}
fun setBackgroundThreadHandler(handler: Handler) {
backgroundThreadHandler = handler
}
override fun getConfigActivelyPreferBadWifi(): Int {
return synchronized(listeners) { configActivelyPreferBadWifi }
}
@ -42,5 +69,17 @@ class MultinetworkPolicyTrackerTestDependencies(private val resources: Resources
}
override fun getResourcesForActiveSubId(res: ConnectivityResources, id: Int): Resources =
resources
resources
override fun readAvoidBadWifiFromCarrierConfig(
context: Context,
subId: Int
): Boolean =
avoidBadWifiCarrierConfigForSubIdMap.getOrDefault(subId, true)
public override fun getAvoidBadWifiFromCarrierConfigFeature(): Boolean =
avoidBadWifiFromCarrierConfigFeature
public override fun getBackgroundThreadHandler(): Handler =
backgroundThreadHandler
}