Support "don't ask again" in the avoid bad wifi dialog.

This contains the following changes:

1. Make NETWORK_AVOID_BAD_WIFI a tristate: 0 means never avoid
   bad wifi, unset means prompt the user, 1 means always avoid.
2. Look at NETWORK_AVOID_BAD_WIFI only if the carrier restricts
   avoiding bad wifi (previously, we relied on the setting being
   null and defaulting to the value of the config variable).
3. Add an avoidUnvalidated bit to NetworkAgentInfo to track
   whether the user has requested switching away from this
   unvalidated network even though avoiding bad wifi is generally
   disabled. This is set to true when the user selects "switch"
   in the dialog without setting the "Don't ask again" checkbox.
4. Add a hidden setAvoidUnvalidated API to ConnectivityManager to
   set the avoidUnvalidated bit.
5. Additional unit test coverage.

Bug: 31075769
Change-Id: I1be60c3016c8095df3c4752330149ce638bd0ce1
This commit is contained in:
Lorenzo Colitti
2016-09-19 01:00:19 +09:00
parent 21924545a2
commit c65750c8a0
5 changed files with 174 additions and 23 deletions

View File

@@ -3197,6 +3197,27 @@ public class ConnectivityManager {
} }
} }
/**
* Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is
* only meaningful if the system is configured not to penalize such networks, e.g., if the
* {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code
* NETWORK_AVOID_BAD_WIFI setting is unset}.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
*
* @param network The network to accept.
*
* @hide
*/
public void setAvoidUnvalidated(Network network) {
try {
mService.setAvoidUnvalidated(network);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** /**
* Resets all connectivity manager settings back to factory defaults. * Resets all connectivity manager settings back to factory defaults.
* @hide * @hide

View File

@@ -161,6 +161,7 @@ interface IConnectivityManager
void releaseNetworkRequest(in NetworkRequest networkRequest); void releaseNetworkRequest(in NetworkRequest networkRequest);
void setAcceptUnvalidated(in Network network, boolean accept, boolean always); void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
void setAvoidUnvalidated(in Network network);
int getRestoreDefaultNetworkDelay(int networkType); int getRestoreDefaultNetworkDelay(int networkType);

View File

@@ -365,6 +365,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/ */
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28; private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
* used to specify whether a network should not be penalized when it becomes unvalidated.
*/
private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
/** /**
* used to ask the user to confirm a connection to an unvalidated network. * used to ask the user to confirm a connection to an unvalidated network.
* obj = network * obj = network
@@ -2681,6 +2686,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
accept ? 1 : 0, always ? 1: 0, network)); accept ? 1 : 0, always ? 1: 0, network));
} }
@Override
public void setAvoidUnvalidated(Network network) {
enforceConnectivityInternalPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
}
private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) { private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
if (DBG) log("handleSetAcceptUnvalidated network=" + network + if (DBG) log("handleSetAcceptUnvalidated network=" + network +
" accept=" + accept + " always=" + always); " accept=" + accept + " always=" + always);
@@ -2721,6 +2732,20 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
private void handleSetAvoidUnvalidated(Network network) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null || nai.lastValidated) {
// Nothing to do. The network either disconnected or revalidated.
return;
}
if (!nai.avoidUnvalidated) {
int oldScore = nai.getCurrentScore();
nai.avoidUnvalidated = true;
rematchAllNetworksAndRequests(nai, oldScore);
sendUpdatedScoreToFactories(nai);
}
}
private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) { private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network); if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
mHandler.sendMessageDelayed( mHandler.sendMessageDelayed(
@@ -2728,28 +2753,31 @@ public class ConnectivityService extends IConnectivityManager.Stub
PROMPT_UNVALIDATED_DELAY_MS); PROMPT_UNVALIDATED_DELAY_MS);
} }
private boolean mAvoidBadWifi; private boolean mAvoidBadWifi = true;
public boolean avoidBadWifi() { public boolean avoidBadWifi() {
return mAvoidBadWifi; return mAvoidBadWifi;
} }
@VisibleForTesting @VisibleForTesting
public boolean updateAvoidBadWifi() { /** Whether the device or carrier configuration disables avoiding bad wifi by default. */
// There are two modes: either we always automatically avoid unvalidated wifi, or we show a public boolean configRestrictsAvoidBadWifi() {
// dialog and don't switch to it. The behaviour is controlled by the NETWORK_AVOID_BAD_WIFI return mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0;
// setting. If the setting has no value, then the value is taken from the config value, }
// which can be changed via OEM/carrier overlays.
// /** Whether we should display a notification when wifi becomes unvalidated. */
// The only valid values for NETWORK_AVOID_BAD_WIFI are null and unset. Currently, the unit public boolean shouldNotifyWifiUnvalidated() {
// test uses 0 in order to avoid having to mock out fetching the carrier setting. return configRestrictsAvoidBadWifi() &&
int defaultAvoidBadWifi = Settings.Global.getString(mContext.getContentResolver(),
mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi); Settings.Global.NETWORK_AVOID_BAD_WIFI) == null;
int avoid = Settings.Global.getInt(mContext.getContentResolver(), }
Settings.Global.NETWORK_AVOID_BAD_WIFI, defaultAvoidBadWifi);
private boolean updateAvoidBadWifi() {
boolean settingAvoidBadWifi = "1".equals(Settings.Global.getString(
mContext.getContentResolver(), Settings.Global.NETWORK_AVOID_BAD_WIFI));
boolean prev = mAvoidBadWifi; boolean prev = mAvoidBadWifi;
mAvoidBadWifi = (avoid == 1); mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
return mAvoidBadWifi != prev; return mAvoidBadWifi != prev;
} }
@@ -2802,7 +2830,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
NetworkCapabilities nc = nai.networkCapabilities; NetworkCapabilities nc = nai.networkCapabilities;
if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc); if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && !avoidBadWifi()) { if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && shouldNotifyWifiUnvalidated()) {
showValidationNotification(nai, NotificationType.LOST_INTERNET); showValidationNotification(nai, NotificationType.LOST_INTERNET);
} }
} }
@@ -2880,6 +2908,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0); handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
break; break;
} }
case EVENT_SET_AVOID_UNVALIDATED: {
handleSetAvoidUnvalidated((Network) msg.obj);
break;
}
case EVENT_PROMPT_UNVALIDATED: { case EVENT_PROMPT_UNVALIDATED: {
handlePromptUnvalidated((Network) msg.obj); handlePromptUnvalidated((Network) msg.obj);
break; break;

View File

@@ -138,12 +138,14 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
public boolean everValidated; public boolean everValidated;
// The result of the last validation attempt on this network (true if validated, false if not). // The result of the last validation attempt on this network (true if validated, false if not).
// This bit exists only because we never unvalidate a network once it's been validated, and that
// is because the network scoring and revalidation code does not (may not?) deal properly with
// networks becoming unvalidated.
// TODO: Fix the network scoring code, remove this, and rename everValidated to validated.
public boolean lastValidated; public boolean lastValidated;
// If true, becoming unvalidated will lower the network's score. This is only meaningful if the
// system is configured not to do this for certain networks, e.g., if the
// config_networkAvoidBadWifi option is set to 0 and the user has not overridden that via
// Settings.Global.NETWORK_AVOID_BAD_WIFI.
public boolean avoidUnvalidated;
// Whether a captive portal was ever detected on this network. // Whether a captive portal was ever detected on this network.
// This is a sticky bit; once set it is never cleared. // This is a sticky bit; once set it is never cleared.
public boolean everCaptivePortalDetected; public boolean everCaptivePortalDetected;
@@ -365,8 +367,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Return true on devices configured to ignore score penalty for wifi networks // Return true on devices configured to ignore score penalty for wifi networks
// that become unvalidated (b/31075769). // that become unvalidated (b/31075769).
private boolean ignoreWifiUnvalidationPenalty() { private boolean ignoreWifiUnvalidationPenalty() {
boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
return isWifi && !mConnService.avoidBadWifi() && everValidated; networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
return isWifi && !avoidBadWifi && everValidated;
} }
// Get the current score for this Network. This may be modified from what the // Get the current score for this Network. This may be modified from what the

View File

@@ -82,6 +82,7 @@ import com.android.server.net.NetworkPinner;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -601,6 +602,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
private class WrappedConnectivityService extends ConnectivityService { private class WrappedConnectivityService extends ConnectivityService {
private WrappedNetworkMonitor mLastCreatedNetworkMonitor; private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
public boolean configRestrictsAvoidBadWifi;
public WrappedConnectivityService(Context context, INetworkManagementService netManager, public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager, INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -656,6 +658,11 @@ public class ConnectivityServiceTest extends AndroidTestCase {
return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj); return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
} }
@Override
public boolean configRestrictsAvoidBadWifi() {
return configRestrictsAvoidBadWifi;
}
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor; return mLastCreatedNetworkMonitor;
} }
@@ -1957,8 +1964,48 @@ public class ConnectivityServiceTest extends AndroidTestCase {
@SmallTest @SmallTest
public void testAvoidBadWifiSetting() throws Exception { public void testAvoidBadWifiSetting() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
mService.configRestrictsAvoidBadWifi = false;
String[] values = new String[] {null, "0", "1"};
for (int i = 0; i < values.length; i++) {
Settings.Global.putInt(cr, settingName, 1);
mService.updateNetworkAvoidBadWifi();
mService.waitForIdle();
String msg = String.format("config=false, setting=%s", values[i]);
assertTrue(msg, mService.avoidBadWifi());
assertFalse(msg, mService.shouldNotifyWifiUnvalidated());
}
mService.configRestrictsAvoidBadWifi = true;
Settings.Global.putInt(cr, settingName, 0);
mService.updateNetworkAvoidBadWifi();
mService.waitForIdle();
assertFalse(mService.avoidBadWifi());
assertFalse(mService.shouldNotifyWifiUnvalidated());
Settings.Global.putInt(cr, settingName, 1);
mService.updateNetworkAvoidBadWifi();
mService.waitForIdle();
assertTrue(mService.avoidBadWifi());
assertFalse(mService.shouldNotifyWifiUnvalidated());
Settings.Global.putString(cr, settingName, null);
mService.updateNetworkAvoidBadWifi();
mService.waitForIdle();
assertFalse(mService.avoidBadWifi());
assertTrue(mService.shouldNotifyWifiUnvalidated());
}
@SmallTest
public void testAvoidBadWifi() throws Exception {
ContentResolver cr = mServiceContext.getContentResolver(); ContentResolver cr = mServiceContext.getContentResolver();
// Pretend we're on a carrier that restricts switching away from bad wifi.
mService.configRestrictsAvoidBadWifi = true;
// File a request for cell to ensure it doesn't go down. // File a request for cell to ensure it doesn't go down.
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
final NetworkRequest cellRequest = new NetworkRequest.Builder() final NetworkRequest cellRequest = new NetworkRequest.Builder()
@@ -1975,8 +2022,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
TestNetworkCallback validatedWifiCallback = new TestNetworkCallback(); TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
// Takes effect on every rematch.
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
mService.updateNetworkAvoidBadWifi();
// Bring up validated cell. // Bring up validated cell.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -2005,7 +2052,42 @@ public class ConnectivityServiceTest extends AndroidTestCase {
NET_CAPABILITY_VALIDATED)); NET_CAPABILITY_VALIDATED));
assertEquals(mCm.getActiveNetwork(), wifiNetwork); assertEquals(mCm.getActiveNetwork(), wifiNetwork);
// Simulate the user selecting "switch" on the dialog. // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
// that we switch back to cell.
mService.configRestrictsAvoidBadWifi = false;
mService.updateNetworkAvoidBadWifi();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
mService.configRestrictsAvoidBadWifi = true;
mService.updateNetworkAvoidBadWifi();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
// Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
mCm.setAvoidUnvalidated(wifiNetwork);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Disconnect and reconnect wifi to clear the one-time switch above.
mWiFiNetworkAgent.disconnect();
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
mCm.reportNetworkConnectivity(wifiNetwork, false);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
mService.updateNetworkAvoidBadWifi(); mService.updateNetworkAvoidBadWifi();
@@ -2017,6 +2099,17 @@ public class ConnectivityServiceTest extends AndroidTestCase {
NET_CAPABILITY_VALIDATED)); NET_CAPABILITY_VALIDATED));
assertEquals(mCm.getActiveNetwork(), cellNetwork); assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Simulate the user turning the cellular fallback setting off and then on.
// We switch to wifi and then to cell.
Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
mService.updateNetworkAvoidBadWifi();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
mService.updateNetworkAvoidBadWifi();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// If cell goes down, we switch to wifi. // If cell goes down, we switch to wifi.
mCellNetworkAgent.disconnect(); mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);