From 199ecfc79facc0023be49282e9a805894c36123e Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 15 Sep 2016 14:02:29 +0900 Subject: [PATCH 1/2] Support displaying a dialog when wifi becomes unvalidated. Bug: 31075769 Change-Id: I7a6e7580769365bea930f638bd44edcaa28df134 --- .../java/android/net/ConnectivityManager.java | 9 +++ .../android/server/ConnectivityService.java | 71 ++++++++++++++++--- .../NetworkNotificationManager.java | 10 ++- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 3c2ac6733c..52d6b56609 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -342,6 +342,15 @@ public class ConnectivityManager { */ public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + /** + * Action used to display a dialog that asks the user whether to avoid a network that is no + * longer validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_LOST_VALIDATION = + "android.net.conn.PROMPT_LOST_VALIDATION"; + /** * Invalid tethering type. * @see #startTethering(int, OnStartTetheringCallback, boolean) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 4c30dc2661..8b1be3b607 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -392,6 +392,11 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_REQUEST_LINKPROPERTIES = 32; private static final int EVENT_REQUEST_NETCAPABILITIES = 33; + /** + * Used internally to (re)configure avoid bad wifi setting. + */ + private static final int EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI = 34; + /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -896,6 +901,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mSettingsObserver.observe( Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON), EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON); + + // Watch for whether to automatically switch away from wifi networks that lose Internet + // access. + mSettingsObserver.observe( + Settings.Global.getUriFor(Settings.Global.NETWORK_AVOID_BAD_WIFI), + EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI); } private synchronized int nextNetworkRequestId() { @@ -2209,6 +2220,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); + final boolean wasValidated = nai.lastValidated; if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") + (msg.obj == null ? "" : " with redirect to " + (String)msg.obj)); if (valid != nai.lastValidated) { @@ -2227,6 +2239,9 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkAgent.CMD_REPORT_NETWORK_STATUS, (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), 0, redirectUrlBundle); + if (wasValidated && !nai.lastValidated) { + handleNetworkUnvalidated(nai); + } } break; } @@ -2706,6 +2721,10 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting public boolean avoidBadWifi() { + // 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. int defaultAvoidBadWifi = mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi); int avoid = Settings.Global.getInt(mContext.getContentResolver(), @@ -2713,6 +2732,31 @@ public class ConnectivityService extends IConnectivityManager.Stub return avoid == 1; } + private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) { + final String action; + switch (type) { + case NO_INTERNET: + action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED; + break; + case LOST_INTERNET: + action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION; + break; + default: + Slog.wtf(TAG, "Unknown notification type " + type); + return; + } + + Intent intent = new Intent(action); + intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName("com.android.settings", + "com.android.settings.wifi.WifiNoInternetDialog"); + + PendingIntent pendingIntent = PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, true); + } + private void handlePromptUnvalidated(Network network) { if (VDBG) log("handlePromptUnvalidated " + network); NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); @@ -2724,18 +2768,16 @@ public class ConnectivityService extends IConnectivityManager.Stub !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { return; } + showValidationNotification(nai, NotificationType.NO_INTERNET); + } - Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED); - intent.setData(Uri.fromParts("netId", Integer.toString(network.netId), null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setClassName("com.android.settings", - "com.android.settings.wifi.WifiNoInternetDialog"); + private void handleNetworkUnvalidated(NetworkAgentInfo nai) { + NetworkCapabilities nc = nai.networkCapabilities; + if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc); - PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); - - mNotifier.showNotification(nai.network.netId, NotificationType.NO_INTERNET, nai, null, - pendingIntent, true); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && !avoidBadWifi()) { + showValidationNotification(nai, NotificationType.LOST_INTERNET); + } } private class InternalHandler extends Handler { @@ -2819,6 +2861,10 @@ public class ConnectivityService extends IConnectivityManager.Stub handleMobileDataAlwaysOn(); break; } + case EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI: { + rematchAllNetworksAndRequests(null, 0); + break; + } case EVENT_REQUEST_LINKPROPERTIES: handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1); break; @@ -4746,7 +4792,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (newNetwork.isSatisfyingRequest(nri.request.requestId)) { // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri", // mark it as no longer satisfying "nri". Because networks are processed by - // rematchAllNetworkAndRequests() in descending score order, "currentNetwork" will + // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will // match "newNetwork" before this loop will encounter a "currentNetwork" with higher // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri". // This means this code doesn't have to handle the case where "currentNetwork" no @@ -5280,6 +5326,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NETWORK_AVOID_BAD_WIFI, null); } @VisibleForTesting diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 99926a971b..f7b01be48d 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -35,7 +35,7 @@ import static android.net.NetworkCapabilities.*; public class NetworkNotificationManager { - public static enum NotificationType { SIGN_IN, NO_INTERNET, NETWORK_SWITCH }; + public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH }; private static final String NOTIFICATION_ID = "Connectivity.Notification"; @@ -91,8 +91,8 @@ public class NetworkNotificationManager { * @param id an identifier that uniquely identifies this notification. This must match * between show and hide calls. We use the NetID value but for legacy callers * we concatenate the range of types with the range of NetIDs. - * @param nai the network with which the notification is associated. For a SIGN_IN or - * NO_INTERNET notification, this is the network we're connecting to. For a + * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET, + * or LOST_INTERNET notification, this is the network we're connecting to. For a * NETWORK_SWITCH notification it's the network that we switched from. When this network * disconnects the notification is removed. * @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null @@ -126,6 +126,10 @@ public class NetworkNotificationManager { if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) { title = r.getString(R.string.wifi_no_internet, 0); details = r.getString(R.string.wifi_no_internet_detailed); + } else if (notifyType == NotificationType.LOST_INTERNET && + transportType == TRANSPORT_WIFI) { + title = r.getString(R.string.wifi_no_internet, 0); + details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.SIGN_IN) { switch (transportType) { case TRANSPORT_WIFI: From 83633c897a904d1a3b29332679e1df8a2e29bcad Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 15 Sep 2016 22:18:09 +0900 Subject: [PATCH 2/2] Unit tests for avoid bad wifi networks setting. Bug: 31075769 Change-Id: I21ba20df59ddce5731ef1ebe8846b3f00578a674 --- .../android/server/ConnectivityService.java | 6 ++ .../server/ConnectivityServiceTest.java | 77 ++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8b1be3b607..66d23a29f4 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2771,6 +2771,12 @@ public class ConnectivityService extends IConnectivityManager.Stub showValidationNotification(nai, NotificationType.NO_INTERNET); } + // TODO: Delete this like updateMobileDataAlwaysOn above. + @VisibleForTesting + void updateNetworkAvoidBadWifi() { + mHandler.sendEmptyMessage(EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI); + } + private void handleNetworkUnvalidated(NetworkAgentInfo nai) { NetworkCapabilities nc = nai.networkCapabilities; if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc); diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 59ccbd93f3..fa2381451a 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.*; import static org.mockito.Mockito.mock; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -137,7 +138,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { @Override public Object getSystemService(String name) { - if (name == Context.CONNECTIVITY_SERVICE) return mCm; + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class); return super.getSystemService(name); } @@ -1953,6 +1955,79 @@ public class ConnectivityServiceTest extends AndroidTestCase { handlerThread.quit(); } + @SmallTest + public void testAvoidBadWifiSetting() throws Exception { + ContentResolver cr = mServiceContext.getContentResolver(); + + // File a request for cell to ensure it doesn't go down. + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + NetworkRequest validatedWifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_VALIDATED) + .build(); + TestNetworkCallback validatedWifiCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); + + // Takes effect on every rematch. + Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0); + + // Bring up validated cell. + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + Network cellNetwork = mCellNetworkAgent.getNetwork(); + + // Bring up validated wifi. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + + // Fail validation on wifi. + mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599; + mCm.reportNetworkConnectivity(wifiNetwork, false); + validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + + // Because avoid bad wifi is off, we don't switch to cellular. + defaultCallback.assertNoCallback(); + assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertEquals(mCm.getActiveNetwork(), wifiNetwork); + + // Simulate the user selecting "switch" on the dialog. + Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); + mService.updateNetworkAvoidBadWifi(); + + // We now switch to cell. + 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); + + // If cell goes down, we switch to wifi. + mCellNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + validatedWifiCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(cellNetworkCallback); + mCm.unregisterNetworkCallback(validatedWifiCallback); + mCm.unregisterNetworkCallback(defaultCallback); + } + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };