Introduce an overlay for actively preferring bad wifi.

This correctly updates when the mcc/mnc change.

Test: MultinetworkPolicyTrackerTest
Change-Id: I11c7ea7074a15975fb68d39eb3c728778d84a516
This commit is contained in:
Chalard Jean
2022-09-01 13:20:14 +09:00
parent 1b8cac56b9
commit 020b93ac63
8 changed files with 175 additions and 7 deletions

View File

@@ -39,6 +39,7 @@ import android.telephony.TelephonyManager;
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 @@ public class MultinetworkPolicyTracker {
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 @@ public class MultinetworkPolicyTracker {
return mAvoidBadWifi;
}
public boolean getActivelyPreferBadWifi() {
return mActivelyPreferBadWifi;
}
// TODO: move this to MultipathPolicyTracker.
public int getMeteredMultipathPreference() {
return mMeteredMultipathPreference;
@@ -179,6 +207,29 @@ public class MultinetworkPolicyTracker {
return (getResourcesForActiveSubId().getInteger(id) == 0);
}
/**
* 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 class MultinetworkPolicyTracker {
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;
}
/**

View File

@@ -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">

View File

@@ -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"/>

View File

@@ -783,7 +783,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
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 @@ public class ConnectivityService extends IConnectivityManager.Stub
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 @@ public class ConnectivityService extends IConnectivityManager.Stub
mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
mContext, mHandler, () -> updateAvoidBadWifi());
mNetworkRanker =
new NetworkRanker(new NetworkRanker.Configuration(activelyPreferBadWifi()));
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -5050,6 +5053,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
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 @@ public class ConnectivityService extends IConnectivityManager.Stub
for (final NetworkOfferInfo noi : offersToUpdate) {
updateOfferScore(noi.offer);
}
mNetworkRanker.setConfiguration(new NetworkRanker.Configuration(activelyPreferBadWifi()));
rematchAllNetworksAndRequests();
}
@@ -5088,6 +5096,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
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;

View File

@@ -38,19 +38,42 @@ import android.annotation.NonNull;
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 @@ public class NetworkRanker {
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.

View File

@@ -35,6 +35,7 @@ import android.test.mock.MockContentResolver
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.argThat
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 @@ class MultinetworkPolicyTrackerTest {
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 @@ class MultinetworkPolicyTrackerTest {
@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 @@ class MultinetworkPolicyTrackerTest {
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

View File

@@ -1838,7 +1838,10 @@ public class ConnectivityServiceTest {
.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);
}
@@ -5620,6 +5623,24 @@ public class ConnectivityServiceTest {
testAvoidBadWifiConfig_controlledBySettings();
}
@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.

View File

@@ -42,7 +42,8 @@ private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportTyp
@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 {