Introduce an overlay for actively preferring bad wifi.
This correctly updates when the mcc/mnc change.
Test: MultinetworkPolicyTrackerTest
Change-Id: I11c7ea7074a15975fb68d39eb3c728778d84a516
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 2dcf932..4217921 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -39,6 +39,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import java.util.Arrays;
import java.util.List;
@@ -79,6 +80,29 @@
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private volatile long mTestAllowBadWifiUntilMs = 0;
+ /**
+ * Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated
+ *
+ * This setting only makes sense if the system is configured not to avoid bad wifis, i.e.
+ * if mAvoidBadWifi is true. If it's not, then no network ever yields to bad wifis
+ * ({@see FullScore#POLICY_YIELD_TO_BAD_WIFI}) and this setting has therefore no effect.
+ *
+ * If this is false, when ranking a bad wifi that never validated against cell data (or any
+ * network that yields to bad wifis), the ranker will prefer cell data. It will prefer wifi
+ * if wifi loses validation later. This behavior avoids the device losing internet access when
+ * walking past a wifi network with no internet access.
+ * This is the default behavior up to Android T, but it can be overridden through an overlay
+ * to behave like below.
+ *
+ * If this is true, then in the same scenario, the ranker will prefer cell data until
+ * the wifi completes its first validation attempt (or the attempt times out after
+ * ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS), then it will prefer the wifi even if it
+ * doesn't provide internet access, unless there is a captive portal on that wifi.
+ * This is the behavior in U and above.
+ */
+ // TODO : implement the behavior.
+ private boolean mActivelyPreferBadWifi;
+
// Mainline module can't use internal HandlerExecutor, so add an identical executor here.
private static class HandlerExecutor implements Executor {
@NonNull
@@ -158,6 +182,10 @@
return mAvoidBadWifi;
}
+ public boolean getActivelyPreferBadWifi() {
+ return mActivelyPreferBadWifi;
+ }
+
// TODO: move this to MultipathPolicyTracker.
public int getMeteredMultipathPreference() {
return mMeteredMultipathPreference;
@@ -180,6 +208,29 @@
}
/**
+ * Whether the device config prefers bad wifi actively, when it doesn't avoid them
+ *
+ * This is only relevant when the device is configured not to avoid bad wifis. In this
+ * case, "actively" preferring a bad wifi means that the device will switch to a bad
+ * wifi it just connected to, as long as it's not a captive portal.
+ *
+ * On U and above this always returns true. On T and below it reads a configuration option.
+ */
+ public boolean configActivelyPrefersBadWifi() {
+ // See the definition of config_activelyPreferBadWifi for a description of its meaning.
+ // On U and above, the config is ignored, and bad wifi is always actively preferred.
+ if (SdkLevel.isAtLeastU()) return true;
+ // TODO: use R.integer.config_activelyPreferBadWifi directly
+ final int id = mResources.get().getIdentifier("config_activelyPreferBadWifi",
+ "integer", mResources.getResourcesContext().getPackageName());
+ // On T and below, 1 means to actively prefer bad wifi, 0 means not to prefer
+ // bad wifi (only stay stuck on it if already on there). This implementation treats
+ // any non-0 value like 1, on the assumption that anybody setting it non-zero wants
+ // the newer behavior.
+ return 0 != getResourcesForActiveSubId().getInteger(id);
+ }
+
+ /**
* Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
* The value works when the time set is more than {@link System.currentTimeMillis()}.
*/
@@ -224,9 +275,13 @@
public boolean updateAvoidBadWifi() {
final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
- final boolean prev = mAvoidBadWifi;
+ final boolean prevAvoid = mAvoidBadWifi;
mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
- return mAvoidBadWifi != prev;
+
+ final boolean prevActive = mActivelyPreferBadWifi;
+ mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+
+ return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
}
/**
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index bff6953..7d777ec 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -78,6 +78,27 @@
Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
<integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
+ <!-- Whether the device should actively prefer bad wifi to good cell on Android 12/13.
+
+ This setting only makes sense if the system is configured not to avoid bad wifis
+ (config_networkAvoidBadWifi=0 and Settings.Global.NETWORK_AVOID_BAD_WIFI=IGNORE
+ or PROMPT), otherwise it's not used.
+
+ On Android 12 and 13, if this is 0, when ranking a bad wifi that never validated against
+ validated mobile data, the system will prefer mobile data. It will prefer wifi if wifi
+ loses validation later. This is the default behavior up to Android 13.
+ This behavior avoids the device losing internet access when walking past a wifi network
+ with no internet access.
+
+ If this is 1, then in the same scenario, the system will prefer mobile data until the wifi
+ completes its first validation attempt (or the attempt times out), and after that it
+ will prefer the wifi even if it doesn't provide internet access, unless there is a captive
+ portal on that wifi.
+
+ On Android 14 and above, the behavior is always like 1, regardless of the value of this
+ setting. -->
+ <integer translatable="false" name="config_activelyPreferBadWifi">1</integer>
+
<!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
be controlled by systemOrSignature apps. -->
<integer-array translatable="false" name="config_protectedNetworks">
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 3389d63..4c85e8c 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -24,6 +24,7 @@
<item type="integer" name="config_networkMeteredMultipathPreference"/>
<item type="array" name="config_networkSupportedKeepaliveCount"/>
<item type="integer" name="config_networkAvoidBadWifi"/>
+ <item type="integer" name="config_activelyPreferBadWifi"/>
<item type="array" name="config_protectedNetworks"/>
<item type="bool" name="config_vehicleInternalNetworkAlwaysRequested"/>
<item type="integer" name="config_networkWakeupPacketMark"/>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 93265e5..cb209e8 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -783,7 +783,8 @@
final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler;
private final DnsManager mDnsManager;
- private final NetworkRanker mNetworkRanker;
+ @VisibleForTesting
+ final NetworkRanker mNetworkRanker;
private boolean mSystemReady;
private Intent mInitialBroadcast;
@@ -1417,7 +1418,6 @@
new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1);
mMetricsLog = logger;
- mNetworkRanker = new NetworkRanker();
final NetworkRequest defaultInternetRequest = createDefaultRequest();
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
@@ -1538,6 +1538,9 @@
mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
mContext, mHandler, () -> updateAvoidBadWifi());
+ mNetworkRanker =
+ new NetworkRanker(new NetworkRanker.Configuration(activelyPreferBadWifi()));
+
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -5050,6 +5053,10 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ private boolean activelyPreferBadWifi() {
+ return mMultinetworkPolicyTracker.getActivelyPreferBadWifi();
+ }
+
/**
* Return whether the device should maintain continuous, working connectivity by switching away
* from WiFi networks having no connectivity.
@@ -5073,6 +5080,7 @@
for (final NetworkOfferInfo noi : offersToUpdate) {
updateOfferScore(noi.offer);
}
+ mNetworkRanker.setConfiguration(new NetworkRanker.Configuration(activelyPreferBadWifi()));
rematchAllNetworksAndRequests();
}
@@ -5088,6 +5096,7 @@
pw.println("Bad Wi-Fi avoidance: " + avoidBadWifi());
pw.increaseIndent();
pw.println("Config restrict: " + configRestrict);
+ pw.println("Actively prefer: " + activelyPreferBadWifi());
final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
String description;
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index f2c6aa1..d56e171 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -38,19 +38,42 @@
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.util.MultinetworkPolicyTracker;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
/**
* A class that knows how to find the best network matching a request out of a list of networks.
*/
public class NetworkRanker {
+ /**
+ * Home for all configurations of NetworkRanker
+ */
+ public static final class Configuration {
+ private final boolean mActivelyPreferBadWifi;
+
+ public Configuration(final boolean activelyPreferBadWifi) {
+ this.mActivelyPreferBadWifi = activelyPreferBadWifi;
+ }
+
+ /**
+ * @see MultinetworkPolicyTracker#getActivelyPreferBadWifi()
+ */
+ // TODO : implement the behavior.
+ public boolean activelyPreferBadWifi() {
+ return mActivelyPreferBadWifi;
+ }
+ }
+ @NonNull private volatile Configuration mConf;
+
// Historically the legacy ints have been 0~100 in principle (though the highest score in
// AOSP has always been 90). This is relied on by VPNs that send a legacy score of 101.
public static final int LEGACY_INT_MAX = 100;
@@ -65,7 +88,22 @@
NetworkCapabilities getCapsNoCopy();
}
- public NetworkRanker() { }
+ public NetworkRanker(@NonNull final Configuration conf) {
+ // Because mConf is volatile, the only way it could be seen null would be an access to it
+ // on some other thread during this constructor. But this is not possible because mConf is
+ // private and `this` doesn't escape this constructor.
+ setConfiguration(conf);
+ }
+
+ public void setConfiguration(@NonNull final Configuration conf) {
+ mConf = Objects.requireNonNull(conf);
+ }
+
+ // There shouldn't be a use case outside of testing
+ @VisibleForTesting
+ public Configuration getConfiguration() {
+ return mConf;
+ }
/**
* Find the best network satisfying this request among the list of passed networks.
diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
index 0dbee5d..f317863 100644
--- a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest
import com.android.connectivity.resources.R
import com.android.internal.util.test.FakeSettingsProvider
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import org.junit.After
@@ -50,7 +51,6 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.doCallRealMethod
-import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
@@ -69,7 +69,10 @@
private val resources = mock(Resources::class.java).also {
doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
eq("config_networkAvoidBadWifi"), eq("integer"), any())
+ doReturn(R.integer.config_activelyPreferBadWifi).`when`(it).getIdentifier(
+ eq("config_activelyPreferBadWifi"), eq("integer"), any())
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 subscriptionManager = mock(SubscriptionManager::class.java).also {
@@ -122,6 +125,7 @@
@Test
fun testUpdateAvoidBadWifi() {
+ doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
assertTrue(tracker.updateAvoidBadWifi())
assertFalse(tracker.avoidBadWifi)
@@ -129,6 +133,24 @@
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
assertTrue(tracker.updateAvoidBadWifi())
assertTrue(tracker.avoidBadWifi)
+
+ if (SdkLevel.isAtLeastU()) {
+ // On U+, the system always prefers bad wifi.
+ assertTrue(tracker.activelyPreferBadWifi)
+ } else {
+ assertFalse(tracker.activelyPreferBadWifi)
+ }
+
+ doReturn(1).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
+ if (SdkLevel.isAtLeastU()) {
+ // On U+, this didn't change the setting
+ assertFalse(tracker.updateAvoidBadWifi())
+ } else {
+ // On T-, this must have changed the setting
+ assertTrue(tracker.updateAvoidBadWifi())
+ }
+ // In all cases, now the system actively prefers bad wifi
+ assertTrue(tracker.activelyPreferBadWifi)
}
@Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index d2a7135..5e681b6 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -1838,7 +1838,10 @@
.getIdentifier(eq("network_switch_type_name"), eq("array"), any());
doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
.getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
+ doReturn(R.integer.config_activelyPreferBadWifi).when(mResources)
+ .getIdentifier(eq("config_activelyPreferBadWifi"), eq("integer"), any());
doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
doReturn(true).when(mResources)
.getBoolean(R.bool.config_cellular_radio_timesharing_capable);
}
@@ -5621,6 +5624,24 @@
}
@Test
+ public void testActivelyPreferBadWifiSetting() throws Exception {
+ doReturn(1).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+
+ doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ if (SdkLevel.isAtLeastU()) {
+ // U+ ignore the setting and always actively prefers bad wifi
+ assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+ } else {
+ assertFalse(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+ }
+ }
+
+ @Test
public void testOffersAvoidsBadWifi() throws Exception {
// Normal mode : the carrier doesn't restrict moving away from bad wifi.
// This has getAvoidBadWifi return true.
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 8b1c510..05c648d 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -42,7 +42,8 @@
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class NetworkRankerTest {
- private val mRanker = NetworkRanker()
+ private val mRanker = NetworkRanker(NetworkRanker.Configuration(
+ false /* activelyPreferBadWifi */))
private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
: NetworkRanker.Scoreable {