diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 52d6b56609..b9e9b28310 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -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}. + * + *

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. * @hide diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index d48c155986..4aabda9eb0 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -161,6 +161,7 @@ interface IConnectivityManager void releaseNetworkRequest(in NetworkRequest networkRequest); void setAcceptUnvalidated(in Network network, boolean accept, boolean always); + void setAvoidUnvalidated(in Network network); int getRestoreDefaultNetworkDelay(int networkType); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 59c8840ebf..a0ef25ed4c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -365,6 +365,11 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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. * obj = network @@ -2681,6 +2686,12 @@ public class ConnectivityService extends IConnectivityManager.Stub 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) { if (DBG) log("handleSetAcceptUnvalidated network=" + network + " 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) { if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network); mHandler.sendMessageDelayed( @@ -2728,28 +2753,31 @@ public class ConnectivityService extends IConnectivityManager.Stub PROMPT_UNVALIDATED_DELAY_MS); } - private boolean mAvoidBadWifi; + private boolean mAvoidBadWifi = true; public boolean avoidBadWifi() { return mAvoidBadWifi; } @VisibleForTesting - public boolean updateAvoidBadWifi() { - // There are two modes: either we always automatically avoid unvalidated wifi, or we show a - // dialog and don't switch to it. The behaviour is controlled by the NETWORK_AVOID_BAD_WIFI - // setting. If the setting has no value, then the value is taken from the config value, - // which can be changed via OEM/carrier overlays. - // - // The only valid values for NETWORK_AVOID_BAD_WIFI are null and unset. Currently, the unit - // test uses 0 in order to avoid having to mock out fetching the carrier setting. - int defaultAvoidBadWifi = - mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi); - int avoid = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.NETWORK_AVOID_BAD_WIFI, defaultAvoidBadWifi); + /** Whether the device or carrier configuration disables avoiding bad wifi by default. */ + public boolean configRestrictsAvoidBadWifi() { + return mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0; + } + + /** Whether we should display a notification when wifi becomes unvalidated. */ + public boolean shouldNotifyWifiUnvalidated() { + return configRestrictsAvoidBadWifi() && + Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.NETWORK_AVOID_BAD_WIFI) == null; + } + + private boolean updateAvoidBadWifi() { + boolean settingAvoidBadWifi = "1".equals(Settings.Global.getString( + mContext.getContentResolver(), Settings.Global.NETWORK_AVOID_BAD_WIFI)); boolean prev = mAvoidBadWifi; - mAvoidBadWifi = (avoid == 1); + mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); return mAvoidBadWifi != prev; } @@ -2802,7 +2830,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkCapabilities nc = nai.networkCapabilities; 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); } } @@ -2880,6 +2908,10 @@ public class ConnectivityService extends IConnectivityManager.Stub handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0); break; } + case EVENT_SET_AVOID_UNVALIDATED: { + handleSetAvoidUnvalidated((Network) msg.obj); + break; + } case EVENT_PROMPT_UNVALIDATED: { handlePromptUnvalidated((Network) msg.obj); break; diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 3503eacb07..9c48aeeefe 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -138,12 +138,14 @@ public class NetworkAgentInfo implements Comparable { public boolean everValidated; // 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; + // 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. // This is a sticky bit; once set it is never cleared. public boolean everCaptivePortalDetected; @@ -365,8 +367,10 @@ public class NetworkAgentInfo implements Comparable { // Return true on devices configured to ignore score penalty for wifi networks // that become unvalidated (b/31075769). private boolean ignoreWifiUnvalidationPenalty() { - boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); - return isWifi && !mConnService.avoidBadWifi() && everValidated; + boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && + 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 diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index fa2381451a..2055d16f32 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -82,6 +82,7 @@ import com.android.server.net.NetworkPinner; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; @@ -601,6 +602,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private class WrappedConnectivityService extends ConnectivityService { private WrappedNetworkMonitor mLastCreatedNetworkMonitor; + public boolean configRestrictsAvoidBadWifi; public WrappedConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, @@ -656,6 +658,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj); } + @Override + public boolean configRestrictsAvoidBadWifi() { + return configRestrictsAvoidBadWifi; + } + public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { return mLastCreatedNetworkMonitor; } @@ -1957,8 +1964,48 @@ public class ConnectivityServiceTest extends AndroidTestCase { @SmallTest 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(); + // 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. final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); final NetworkRequest cellRequest = new NetworkRequest.Builder() @@ -1975,8 +2022,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { TestNetworkCallback validatedWifiCallback = new TestNetworkCallback(); mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); - // Takes effect on every rematch. Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0); + mService.updateNetworkAvoidBadWifi(); // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); @@ -2005,7 +2052,42 @@ public class ConnectivityServiceTest extends AndroidTestCase { NET_CAPABILITY_VALIDATED)); 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); mService.updateNetworkAvoidBadWifi(); @@ -2017,6 +2099,17 @@ public class ConnectivityServiceTest extends AndroidTestCase { NET_CAPABILITY_VALIDATED)); 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. mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);