diff --git a/service/Android.bp b/service/Android.bp index 8b469e4e69..e71a6c176f 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -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", diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 3b5f4030ab..97d67b5e08 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -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)) { diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java index 93018bb61a..60d27c353d 100644 --- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java +++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java @@ -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 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; + } } /** diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt index f19ba4f5c7..95a6d8c8bb 100644 --- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt +++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt @@ -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() + + // 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) } } diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt index 4c82c7688d..7285cbe32b 100644 --- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt +++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt @@ -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>() + private var avoidBadWifiFromCarrierConfigFeature = false + private val avoidBadWifiCarrierConfigForSubIdMap = HashMap() + 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 }