Implement network avoid bad wifi from carrier config

Flag: android.net.platform.flags.avoid_bad_wifi_from_carrier_config
Bug: 410976315
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.connectivity.MultinetworkPolicyTrackerTest
Change-Id: I89c579a8d5c7299303278280c463aa4c7c5bea99
This commit is contained in:
Jimi Chen 2025-04-15 05:35:21 +00:00
commit 0881fc0a7d
5 changed files with 493 additions and 47 deletions

View file

@ -186,6 +186,7 @@ java_library {
static_libs: [
// Do not add libs here if they are already included
// in framework-connectivity
"android.net.platform.flags-aconfig-java-export",
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",

View file

@ -6711,7 +6711,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
pw.println("Config restrict: " + configRestrict);
pw.println("Actively prefer bad wifi: " + activelyPreferBadWifi());
final String settingValue = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
final String settingValue = mMultinetworkPolicyTracker.readAvoidBadWifiFromSettings();
String description;
// Can't use a switch statement because strings are legal case labels, but null is not.
if ("0".equals(settingValue)) {

View file

@ -16,10 +16,12 @@
package com.android.server.connectivity;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@ -29,19 +31,26 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.net.platform.flags.Flags;
import android.os.Build;
import android.os.Handler;
import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BackgroundThread;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.HandlerUtils;
import java.util.Arrays;
import java.util.List;
@ -66,6 +75,23 @@ import java.util.concurrent.RejectedExecutionException;
* @hide
*/
public class MultinetworkPolicyTracker {
private class CarrierConfigChangeListener
implements CarrierConfigManager.CarrierConfigChangeListener {
/**
* Must be called on the background thread.
*/
@Override
public void onCarrierConfigChanged(
int slotIndex, int subId, int carrierId, int specificCarrierId) {
updateAvoidBadWifiFromCarrierConfigBackground(subId);
}
}
private enum AvoidBadWifiSource {
FROM_RESOURCE,
FROM_CARRIER_CONFIG,
}
private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
// See Dependencies#getConfigActivelyPreferBadWifi
@ -79,7 +105,16 @@ public class MultinetworkPolicyTracker {
private final ContentResolver mResolver;
private final SettingObserver mSettingObserver;
private final BroadcastReceiver mBroadcastReceiver;
private final boolean mAvoidBadWifiFromCarrierConfigFeature;
private final boolean mHasTelephonySubscription;
// This will be null if the FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG is off
private final @Nullable CarrierConfigManager mCarrierConfigManager;
private final @Nullable CarrierConfigChangeListener mCarrierConfigChangeListener;
// This will be accessed by background and handler thread
@GuardedBy("mAvoidBadWifiCarrierConfigForSubIdMap")
private final ArrayMap<Integer, Boolean> mAvoidBadWifiCarrierConfigForSubIdMap =
new ArrayMap<>();
private volatile boolean mAvoidBadWifi = true;
private volatile int mMeteredMultipathPreference;
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@ -90,6 +125,11 @@ public class MultinetworkPolicyTracker {
*/
@VisibleForTesting
public static class Dependencies {
private boolean isCarrierConfigValid(PersistableBundle config) {
return config == null || config.isEmpty()
|| !CarrierConfigManager.isConfigForIdentifiedCarrier(config);
}
/**
* @see DeviceConfigUtils#getDeviceConfigPropertyInt
*/
@ -119,6 +159,44 @@ public class MultinetworkPolicyTracker {
return SubscriptionManager.getResourcesForSubId(
resources.getResourcesContext(), activeSubId);
}
@VisibleForTesting
protected boolean readAvoidBadWifiFromCarrierConfig(
@NonNull final Context context, final int subId) {
// Defaults to true to avoid potentially poor Wi-Fi and improve user experience.
final boolean defaultConfig = true;
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return defaultConfig;
}
PersistableBundle config = null;
final CarrierConfigManager ccm = context.getSystemService(CarrierConfigManager.class);
try {
config = ccm.getConfigForSubId(subId,
CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL
);
} catch (RuntimeException e) {
Log.e(TAG, "Failed to get config from carrier config", e);
}
if (isCarrierConfigValid(config)) {
// If the "avoid bad Wi-Fi" setting is not defined in the carrier-specific config,
// retrieve the default configuration.
config = CarrierConfigManager.getDefaultConfig();
}
return config.getBoolean(CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL, defaultConfig);
}
protected boolean getAvoidBadWifiFromCarrierConfigFeature() {
return Flags.avoidBadWifiFromCarrierConfig();
}
protected Handler getBackgroundThreadHandler() {
return BackgroundThread.getHandler();
}
}
private final Dependencies mDeps;
@ -164,7 +242,7 @@ public class MultinetworkPolicyTracker {
protected class ActiveDataSubscriptionIdListener extends TelephonyCallback
implements TelephonyCallback.ActiveDataSubscriptionIdListener {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
public void onActiveDataSubscriptionIdChanged(final int subId) {
mActiveSubId = subId;
reevaluateInternal();
}
@ -193,8 +271,21 @@ public class MultinetworkPolicyTracker {
}
};
updateAvoidBadWifi();
updateMeteredMultipathPreference();
mHasTelephonySubscription =
mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION);
mAvoidBadWifiFromCarrierConfigFeature = mDeps.getAvoidBadWifiFromCarrierConfigFeature();
mCarrierConfigManager = mAvoidBadWifiFromCarrierConfigFeature
? mContext.getSystemService(CarrierConfigManager.class)
: null;
mCarrierConfigChangeListener = mAvoidBadWifiFromCarrierConfigFeature
? new CarrierConfigChangeListener()
: null;
if (!mAvoidBadWifiFromCarrierConfigFeature) {
updateAvoidBadWifi();
updateMeteredMultipathPreference();
}
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@ -212,8 +303,20 @@ public class MultinetworkPolicyTracker {
final Executor handlerExecutor = new HandlerExecutor(mHandler);
mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
handlerExecutor, new ActiveDataSubscriptionIdListener());
mDeps.addOnDevicePropertiesChangedListener(handlerExecutor,
if (mAvoidBadWifiFromCarrierConfigFeature) {
mCarrierConfigManager.registerCarrierConfigChangeListener(
BackgroundThread.getExecutor(), mCarrierConfigChangeListener
);
// This ensures the latest carrier configuration is read.
final int subId = mActiveSubId;
mDeps.getBackgroundThreadHandler().post(() ->
updateAvoidBadWifiFromCarrierConfigBackground(subId));
} else {
mDeps.addOnDevicePropertiesChangedListener(handlerExecutor,
properties -> reevaluateInternal());
}
reevaluate();
}
@ -222,6 +325,11 @@ public class MultinetworkPolicyTracker {
mResolver.unregisterContentObserver(mSettingObserver);
mContext.unregisterReceiver(mBroadcastReceiver);
if (mAvoidBadWifiFromCarrierConfigFeature) {
mCarrierConfigManager.unregisterCarrierConfigChangeListener(
mCarrierConfigChangeListener
);
}
}
public boolean getAvoidBadWifi() {
@ -287,10 +395,13 @@ public class MultinetworkPolicyTracker {
* Whether we should display a notification when wifi becomes unvalidated.
*/
public boolean shouldNotifyWifiUnvalidated() {
return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
return configRestrictsAvoidBadWifi() && readAvoidBadWifiFromSettings() == null;
}
public String getAvoidBadWifiSetting() {
/**
* Retrieves the "avoid bad Wi-Fi" setting from the global settings.
*/
public String readAvoidBadWifiFromSettings() {
return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
}
@ -332,20 +443,119 @@ public class MultinetworkPolicyTracker {
updateMeteredMultipathPreference();
}
public boolean updateAvoidBadWifi() {
final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
final boolean prevAvoid = mAvoidBadWifi;
mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
/**
* Reads the avoid bad Wi-Fi setting from the cache for a specific subscription ID.
* If no value is found for the given subId, it defaults to true.
*/
private boolean readAvoidBadWifiFromCache(int subId) {
synchronized (mAvoidBadWifiCarrierConfigForSubIdMap) {
return mAvoidBadWifiCarrierConfigForSubIdMap.getOrDefault(subId, true);
}
}
final boolean prevActive = mActivelyPreferBadWifi;
final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
if (null == deviceConfigPreferBadWifi) {
mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
} else {
mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
/**
* Updates the cached avoid bad Wi-Fi setting for a specific subscription ID.
*/
private void updateAvoidBadWifiCache(int subId, boolean enable) {
synchronized (mAvoidBadWifiCarrierConfigForSubIdMap) {
mAvoidBadWifiCarrierConfigForSubIdMap.put(subId, enable);
}
}
/**
* Determines the source of the avoid bad Wi-Fi setting.
* It returns FROM_CARRIER_CONFIG if the carrier config feature is enabled,
* otherwise it returns FROM_RESOURCE.
*
*/
private AvoidBadWifiSource getAvoidBadWifiSource() {
return mAvoidBadWifiFromCarrierConfigFeature
? AvoidBadWifiSource.FROM_CARRIER_CONFIG
: AvoidBadWifiSource.FROM_RESOURCE;
}
/**
* Updates the local cache of the "avoid bad Wi-Fi" setting from the carrier config
* for a specific subscription ID.
* Must be called on the handler thread.
*/
private void updateAvoidBadWifiFromCarrierConfig(int subId, boolean enable) {
updateAvoidBadWifiCache(subId, enable);
if (subId == mActiveSubId) {
reevaluateInternal();
}
}
/**
* Retrieves the "avoid bad Wi-Fi" setting from the carrier configuration for the given subId,
* and updates the local cache asynchronously on the handler thread.
* Must be called on the background thread, because it makes an IPC to the phone process.
* It must not be called on the CS handler thread.
*/
private void updateAvoidBadWifiFromCarrierConfigBackground(int subId) {
HandlerUtils.ensureRunningOnHandlerThread(mDeps.getBackgroundThreadHandler());
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
// CarrierConfigManager#getConfigForSubId() is supported
// only when system has FEATURE_TELEPHONY_SUBSCRIPTION
final boolean config = !mHasTelephonySubscription
|| mDeps.readAvoidBadWifiFromCarrierConfig(mContext, subId);
mHandler.post(() -> updateAvoidBadWifiFromCarrierConfig(subId, config));
}
/**
* Updates the "avoid bad Wi-Fi" setting.
* Depending on whether the carrier config feature is enabled, this method uses different logic:
*
* If carrier config feature is ON (Android U+ and 25Q4+):
* - Forces "actively prefer bad Wi-Fi" to true.
* - Checks a system setting first. If it is enabled, we should avoid bad Wi-Fi.
* - If the system setting isn't available, it uses a cached value from the carrier config
* for the current subId.
*
* If carrier config feature is OFF:
* - "Avoid bad Wi-Fi" is true if the system setting is enabled OR
* if the carrier doesn't restrict avoiding bad Wi-Fi.
* - "Actively prefer bad Wi-Fi" is based on a device-specific setting,
* falling back to the carrier config if the device setting isn't available.
*
* Returns true if either the "avoid bad Wi-Fi" or "actively prefer bad Wi-Fi" setting changed,
* false otherwise.
*/
public boolean updateAvoidBadWifi() {
final boolean prevAvoid = mAvoidBadWifi;
if (getAvoidBadWifiSource() == AvoidBadWifiSource.FROM_CARRIER_CONFIG) {
// Force update activelyPreferBadWifi since it will always be true in Android U+,
// and mAvoidBadWifiFromCarrierConfigFeature is a trunk stable flag
// that only exists in 25Q4+
mActivelyPreferBadWifi = true;
final String settingAvoidBadWifiStr = readAvoidBadWifiFromSettings();
if (settingAvoidBadWifiStr != null) {
mAvoidBadWifi = "1".equals(settingAvoidBadWifiStr);
} else {
// Retrieve the avoid bad Wi-Fi setting from the local cache to avoid potential
// issues or blocking from the IPC call getAvoidBadWifiCarrierConfigForSubId().
mAvoidBadWifi = readAvoidBadWifiFromCache(mActiveSubId);
}
return mAvoidBadWifi != prevAvoid;
} else {
final boolean settingAvoidBadWifi = "1".equals(readAvoidBadWifiFromSettings());
mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
final boolean prevActive = mActivelyPreferBadWifi;
final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
if (null == deviceConfigPreferBadWifi) {
mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
} else {
mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
}
return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
}
}
/**

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
}