diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fb0821e735..3f8410f0cc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -426,6 +426,16 @@ public class ConnectivityManager { public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.conn.PROMPT_LOST_VALIDATION"; + /** + * Action used to display a dialog that asks the user whether to stay connected to a network + * that has not validated. This intent is used to start the dialog in settings via + * startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = + "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY"; + /** * Invalid tethering type. * @see #startTethering(int, boolean, OnStartTetheringCallback) @@ -4034,7 +4044,7 @@ public class ConnectivityManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { try { mService.setAcceptUnvalidated(network, accept, always); @@ -4043,6 +4053,29 @@ public class ConnectivityManager { } } + /** + * Informs the system whether it should consider the network as validated even if it only has + * partial connectivity. If {@code accept} is true, then the network will be considered as + * validated even if connectivity is only partial. If {@code always} is true, then the choice + * is remembered, so that the next time the user connects to this network, the system will + * switch to it. + * + * @param network The network to accept. + * @param accept Whether to consider the network as validated even if it has partial + * connectivity. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) { + try { + mService.setAcceptPartialConnectivity(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * 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 @@ -4053,7 +4086,7 @@ public class ConnectivityManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAvoidUnvalidated(Network network) { try { mService.setAvoidUnvalidated(network); diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a425a91ef3..403b44d6d7 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -176,6 +176,7 @@ interface IConnectivityManager void releaseNetworkRequest(in NetworkRequest networkRequest); void setAcceptUnvalidated(in Network network, boolean accept, boolean always); + void setAcceptPartialConnectivity(in Network network, boolean accept, boolean always); void setAvoidUnvalidated(in Network network); void startCaptivePortalApp(in Network network); void startCaptivePortalAppInternal(in Network network, in Bundle appExtras); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index eb0fe33df0..c57ae0c9fc 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -143,7 +143,8 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_NOT_CONGESTED, NET_CAPABILITY_NOT_SUSPENDED, NET_CAPABILITY_OEM_PAID, - NET_CAPABILITY_MCX + NET_CAPABILITY_MCX, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, }) public @interface NetCapability { } @@ -304,8 +305,15 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_MCX = 23; + /** + * Indicates that this network was tested to only provide partial connectivity. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_MCX; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -320,7 +328,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_ROAMING) | (1 << NET_CAPABILITY_FOREGROUND) | (1 << NET_CAPABILITY_NOT_CONGESTED) - | (1 << NET_CAPABILITY_NOT_SUSPENDED); + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -374,6 +383,15 @@ public final class NetworkCapabilities implements Parcelable { (1 << NET_CAPABILITY_SUPL) | (1 << NET_CAPABILITY_WIFI_P2P); + /** + * Capabilities that are managed by ConnectivityService. + */ + private static final long CONNECTIVITY_MANAGED_CAPABILITIES = + (1 << NET_CAPABILITY_VALIDATED) + | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) + | (1 << NET_CAPABILITY_FOREGROUND) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); + /** * Adds the given capability to this {@code NetworkCapability} instance. * Multiple capabilities may be applied sequentially. Note that when searching @@ -507,6 +525,14 @@ public final class NetworkCapabilities implements Parcelable { && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); } + /** + * Check if this NetworkCapabilities has system managed capabilities or not. + * @hide + */ + public boolean hasConnectivityManagedCapability() { + return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); + } + /** Note this method may result in having the same capability in wanted and unwanted lists. */ private void combineNetCapabilities(NetworkCapabilities nc) { this.mNetworkCapabilities |= nc.mNetworkCapabilities; @@ -1599,31 +1625,32 @@ public final class NetworkCapabilities implements Parcelable { */ public static String capabilityNameOf(@NetCapability int capability) { switch (capability) { - case NET_CAPABILITY_MMS: return "MMS"; - case NET_CAPABILITY_SUPL: return "SUPL"; - case NET_CAPABILITY_DUN: return "DUN"; - case NET_CAPABILITY_FOTA: return "FOTA"; - case NET_CAPABILITY_IMS: return "IMS"; - case NET_CAPABILITY_CBS: return "CBS"; - case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P"; - case NET_CAPABILITY_IA: return "IA"; - case NET_CAPABILITY_RCS: return "RCS"; - case NET_CAPABILITY_XCAP: return "XCAP"; - case NET_CAPABILITY_EIMS: return "EIMS"; - case NET_CAPABILITY_NOT_METERED: return "NOT_METERED"; - case NET_CAPABILITY_INTERNET: return "INTERNET"; - case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED"; - case NET_CAPABILITY_TRUSTED: return "TRUSTED"; - case NET_CAPABILITY_NOT_VPN: return "NOT_VPN"; - case NET_CAPABILITY_VALIDATED: return "VALIDATED"; - case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; - case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; - case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; - case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED"; - case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED"; - case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; - case NET_CAPABILITY_MCX: return "MCX"; - default: return Integer.toString(capability); + case NET_CAPABILITY_MMS: return "MMS"; + case NET_CAPABILITY_SUPL: return "SUPL"; + case NET_CAPABILITY_DUN: return "DUN"; + case NET_CAPABILITY_FOTA: return "FOTA"; + case NET_CAPABILITY_IMS: return "IMS"; + case NET_CAPABILITY_CBS: return "CBS"; + case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P"; + case NET_CAPABILITY_IA: return "IA"; + case NET_CAPABILITY_RCS: return "RCS"; + case NET_CAPABILITY_XCAP: return "XCAP"; + case NET_CAPABILITY_EIMS: return "EIMS"; + case NET_CAPABILITY_NOT_METERED: return "NOT_METERED"; + case NET_CAPABILITY_INTERNET: return "INTERNET"; + case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED"; + case NET_CAPABILITY_TRUSTED: return "TRUSTED"; + case NET_CAPABILITY_NOT_VPN: return "NOT_VPN"; + case NET_CAPABILITY_VALIDATED: return "VALIDATED"; + case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; + case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; + case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; + case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED"; + case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED"; + case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; + case NET_CAPABILITY_MCX: return "MCX"; + case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; + default: return Integer.toString(capability); } } diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java index ed0b61efd7..9ba3bd940a 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkMisc.java @@ -51,6 +51,12 @@ public class NetworkMisc implements Parcelable { */ public boolean acceptUnvalidated; + /** + * Whether the user explicitly set that this network should be validated even if presence of + * only partial internet connectivity. + */ + public boolean acceptPartialConnectivity; + /** * Set to avoid surfacing the "Sign in to network" notification. * if carrier receivers/apps are registered to handle the carrier-specific provisioning diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 199477ce26..72f7a68ad2 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -25,6 +25,7 @@ import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; @@ -34,6 +35,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkPolicyManager.RULE_NONE; @@ -489,6 +491,15 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public static final int EVENT_TIMEOUT_NOTIFICATION = 44; + /** + * Used to specify whether a network should be used even if connectivity is partial. + * arg1 = whether to accept the network if its connectivity is partial (1 for true or 0 for + * false) + * arg2 = whether to remember this choice in the future (1 for true or 0 for false) + * obj = network + */ + private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 45; + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. @@ -2489,9 +2500,7 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (msg.what) { case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: { final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj; - if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) || - networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) || - networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) { + if (networkCapabilities.hasConnectivityManagedCapability()) { Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability."); } updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities); @@ -2516,6 +2525,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } nai.networkMisc.explicitlySelected = true; nai.networkMisc.acceptUnvalidated = msg.arg1 == 1; + // Mark the network as temporarily accepting partial connectivity so that it + // will be validated (and possibly become default) even if it only provides + // partial internet access. Note that if user connects to partial connectivity + // and choose "don't ask again", then wifi disconnected by some reasons(maybe + // out of wifi coverage) and if the same wifi is available again, the device + // will auto connect to this wifi even though the wifi has "no internet". + // TODO: Evaluate using a separate setting in IpMemoryStore. + nai.networkMisc.acceptPartialConnectivity = msg.arg1 == 1; break; } case NetworkAgent.EVENT_SOCKET_KEEPALIVE: { @@ -2533,6 +2550,23 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); if (nai == null) break; + final boolean partialConnectivity = + (msg.arg1 == NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY) + // If user accepts partial connectivity network, NetworkMonitor + // will skip https probing. It will make partial connectivity + // network becomes valid. But user still need to know this + // network is limited. So, it's needed to refer to + // acceptPartialConnectivity to add + // NET_CAPABILITY_PARTIAL_CONNECTIVITY into NetworkCapabilities + // of this network. So that user can see "Limited connection" + // in the settings. + || (nai.networkMisc.acceptPartialConnectivity + && nai.partialConnectivity); + // Once a network is determined to have partial connectivity, it cannot + // go back to full connectivity without a disconnect. + final boolean partialConnectivityChange = + (partialConnectivity && !nai.partialConnectivity); + final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; final boolean wasDefault = isDefaultNetwork(nai); @@ -2541,6 +2575,17 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.captivePortalLoginNotified = true; showNetworkNotification(nai, NotificationType.LOGGED_IN); } + // If this network has just connected and partial connectivity has just been + // detected, tell NetworkMonitor if the user accepted partial connectivity on a + // previous connect. + if ((msg.arg1 == NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY) + && nai.networkMisc.acceptPartialConnectivity) { + try { + nai.networkMonitor().notifyAcceptPartialConnectivity(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; @@ -2570,6 +2615,9 @@ public class ConnectivityService extends IConnectivityManager.Stub mNotifier.clearNotification(nai.network.netId, NotificationType.LOST_INTERNET); } + } else if (partialConnectivityChange) { + nai.partialConnectivity = partialConnectivity; + updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); } updateInetCondition(nai); // Let the NetworkAgent know the state of its network @@ -2608,7 +2656,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (!visible) { // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other - // notifications belong to the same network may be cleared unexpected. + // notifications belong to the same network may be cleared unexpectedly. mNotifier.clearNotification(netId, NotificationType.SIGN_IN); mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH); } else { @@ -3225,14 +3273,21 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { - enforceConnectivityInternalPermission(); + enforceNetworkStackSettingsOrSetup(); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED, encodeBool(accept), encodeBool(always), network)); } + @Override + public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) { + enforceNetworkStackSettingsOrSetup(); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY, + encodeBool(accept), encodeBool(always), network)); + } + @Override public void setAvoidUnvalidated(Network network) { - enforceConnectivityInternalPermission(); + enforceNetworkStackSettingsOrSetup(); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network)); } @@ -3258,6 +3313,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (accept != nai.networkMisc.acceptUnvalidated) { int oldScore = nai.getCurrentScore(); nai.networkMisc.acceptUnvalidated = accept; + // If network becomes partial connectivity and user already accepted to use this + // network, we should respect the user's option and don't need to popup the + // PARTIAL_CONNECTIVITY notification to user again. + nai.networkMisc.acceptPartialConnectivity = accept; rematchAllNetworksAndRequests(nai, oldScore); sendUpdatedScoreToFactories(nai); } @@ -3276,6 +3335,48 @@ public class ConnectivityService extends IConnectivityManager.Stub } + private void handleSetAcceptPartialConnectivity(Network network, boolean accept, + boolean always) { + if (DBG) { + log("handleSetAcceptPartialConnectivity network=" + network + " accept=" + accept + + " always=" + always); + } + + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) { + // Nothing to do. + return; + } + + if (nai.lastValidated) { + // The network validated while the dialog box was up. Take no action. + return; + } + + if (accept != nai.networkMisc.acceptPartialConnectivity) { + nai.networkMisc.acceptPartialConnectivity = accept; + } + + // TODO: Use the current design or save the user choice into IpMemoryStore. + if (always) { + nai.asyncChannel.sendMessage( + NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept)); + } + + if (!accept) { + // Tell the NetworkAgent to not automatically reconnect to the network. + nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT); + // Tear down the network. + teardownUnneededNetwork(nai); + } else { + try { + nai.networkMonitor().notifyAcceptPartialConnectivity(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + private void handleSetAvoidUnvalidated(Network network) { NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null || nai.lastValidated) { @@ -3446,6 +3547,9 @@ public class ConnectivityService extends IConnectivityManager.Stub case LOST_INTERNET: action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION; break; + case PARTIAL_CONNECTIVITY: + action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY; + break; default: Slog.wtf(TAG, "Unknown notification type " + type); return; @@ -3468,22 +3572,36 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) log("handlePromptUnvalidated " + network); NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - // Only prompt if the network is unvalidated and was explicitly selected by the user, and if - // we haven't already been told to switch to it regardless of whether it validated or not. - // Also don't prompt on captive portals because we're already prompting the user to sign in. - if (nai == null || nai.everValidated || nai.everCaptivePortalDetected || - !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { + // Only prompt if the network is unvalidated or network has partial internet connectivity + // and was explicitly selected by the user, and if we haven't already been told to switch + // to it regardless of whether it validated or not. Also don't prompt on captive portals + // because we're already prompting the user to sign in. + if (nai == null || nai.everValidated || nai.everCaptivePortalDetected + || !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated + || nai.networkMisc.acceptPartialConnectivity) { return; } - showNetworkNotification(nai, NotificationType.NO_INTERNET); + // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when + // NetworkMonitor detects the network is partial connectivity. Need to change the design to + // popup the notification immediately when the network is partial connectivity. + if (nai.partialConnectivity) { + // Treat PARTIAL_CONNECTIVITY as NO_INTERNET temporary until Settings has been updated. + // TODO: Need to change back to PARTIAL_CONNECTIVITY when Settings part is merged. + showNetworkNotification(nai, NotificationType.NO_INTERNET); + } else { + showNetworkNotification(nai, NotificationType.NO_INTERNET); + } } private void handleNetworkUnvalidated(NetworkAgentInfo nai) { NetworkCapabilities nc = nai.networkCapabilities; if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc); - if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && - mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) { + if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return; + } + + if (mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) { showNetworkNotification(nai, NotificationType.LOST_INTERNET); } } @@ -3575,6 +3693,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2)); break; } + case EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY: { + Network network = (Network) msg.obj; + handleSetAcceptPartialConnectivity(network, toBool(msg.arg1), + toBool(msg.arg2)); + break; + } case EVENT_SET_AVOID_UNVALIDATED: { handleSetAvoidUnvalidated((Network) msg.obj); break; @@ -5531,6 +5655,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { newNc.addCapability(NET_CAPABILITY_NOT_SUSPENDED); } + if (nai.partialConnectivity) { + newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + } else { + newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + } return newNc; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 6ef9fbbf0d..8f2825ca72 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -156,6 +156,9 @@ public class NetworkAgentInfo implements Comparable { // last detected. public boolean captivePortalLoginNotified; + // Set to true when partial connectivity was detected. + public boolean partialConnectivity; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being // satisfied by a higher-scoring network. so as to allow communication to wrap up before the // network is taken down. This usually only happens to the default network. Lingering ends with @@ -595,6 +598,8 @@ public class NetworkAgentInfo implements Comparable { for (LingerTimer timer : mLingerTimers) { pw.println(timer); } } + // TODO: Print shorter members first and only print the boolean variable which value is true + // to improve readability. public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} " + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " @@ -607,6 +612,8 @@ public class NetworkAgentInfo implements Comparable { + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} " + + "partialConnectivity{" + partialConnectivity + "} " + + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} " + "clat{" + clatd + "} " + "}"; } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index b50477bc12..053da0d5b1 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -47,8 +47,9 @@ public class NetworkNotificationManager { LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET), NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH), NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET), - SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN), - LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN); + LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN), + PARTIAL_CONNECTIVITY(SystemMessage.NOTE_NETWORK_PARTIAL_CONNECTIVITY), + SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN); public final int eventId; @@ -169,11 +170,18 @@ public class NetworkNotificationManager { CharSequence details; int icon = getIcon(transportType); if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, 0); + title = r.getString(R.string.wifi_no_internet, + WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID())); details = r.getString(R.string.wifi_no_internet_detailed); + } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY + && transportType == TRANSPORT_WIFI) { + title = r.getString(R.string.network_partial_connectivity, + WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID())); + details = r.getString(R.string.network_partial_connectivity_detailed); } else if (notifyType == NotificationType.LOST_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, 0); + title = r.getString(R.string.wifi_no_internet, + WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID())); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.SIGN_IN) { switch (transportType) { @@ -316,6 +324,8 @@ public class NetworkNotificationManager { } switch (t) { case SIGN_IN: + return 5; + case PARTIAL_CONNECTIVITY: return 4; case NO_INTERNET: return 3; diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index 4d4915b83c..ad76388b3c 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -20,6 +20,7 @@ import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; @@ -27,6 +28,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; @@ -334,6 +336,24 @@ public class NetworkCapabilitiesTest { assertTrue(request.satisfiedByNetworkCapabilities(network)); } + @Test + public void testConnectivityManagedCapabilities() { + NetworkCapabilities nc = new NetworkCapabilities(); + assertFalse(nc.hasConnectivityManagedCapability()); + // Check every single system managed capability. + nc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + nc.addCapability(NET_CAPABILITY_FOREGROUND); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_FOREGROUND); + nc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + nc.addCapability(NET_CAPABILITY_VALIDATED); + assertTrue(nc.hasConnectivityManagedCapability()); + } + @Test public void testEqualsNetCapabilities() { NetworkCapabilities nc1 = new NetworkCapabilities(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index d29234fbf1..985d66739d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -28,6 +28,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; @@ -43,6 +44,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; @@ -443,6 +445,11 @@ public class ConnectivityServiceTest { mNmValidationRedirectUrl = redirectUrl; } + void setNetworkPartial() { + mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; + mNmValidationRedirectUrl = null; + } + MockNetworkAgent(int transport) { this(transport, new LinkProperties()); } @@ -485,6 +492,7 @@ public class ConnectivityServiceTest { try { doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(); doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); + doAnswer(validateAnswer).when(mNetworkMonitor).notifyAcceptPartialConnectivity(); } catch (RemoteException e) { fail(e.getMessage()); } @@ -670,6 +678,11 @@ public class ConnectivityServiceTest { connect(false); } + public void connectWithPartialConnectivity() { + setNetworkPartial(); + connect(false); + } + public void suspend() { mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); @@ -2536,6 +2549,106 @@ public class ConnectivityServiceTest { verifyActiveNetwork(TRANSPORT_CELLULAR); } + @Test + public void testPartialConnectivity() { + // Register network callback. + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + // Bring up validated mobile data. + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + // Bring up wifi with partial connectivity. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + + // Mobile data should be the default network. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + callback.assertNoCallback(); + + // If the user chooses yes to use this partial connectivity wifi, switch the default + // network to wifi and check if wifi becomes valid or not. + mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, + false /* always */); + // With https probe disabled, NetworkMonitor should pass the network validation with http + // probe. + mWiFiNetworkAgent.setNetworkValid(); + waitForIdle(); + try { + verify(mWiFiNetworkAgent.mNetworkMonitor, + timeout(TIMEOUT_MS).times(1)).notifyAcceptPartialConnectivity(); + } catch (RemoteException e) { + fail(e.getMessage()); + } + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, + mWiFiNetworkAgent); + assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Disconnect and reconnect wifi with partial connectivity again. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + + // Mobile data should be the default network. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // If the user chooses no, disconnect wifi immediately. + mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */, + false /* always */); + callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + + // If user accepted partial connectivity before, and device reconnects to that network + // again, but now the network has full connectivity. The network shouldn't contain + // NET_CAPABILITY_PARTIAL_CONNECTIVITY. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + // acceptUnvalidated is also used as setting for accepting partial networks. + mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); + // Wifi should be the default network. + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + + // If user accepted partial connectivity before, and now the device reconnects to the + // partial connectivity network. The network should be valid and contain + // NET_CAPABILITY_PARTIAL_CONNECTIVITY. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // TODO: If the user accepted partial connectivity, we shouldn't switch to wifi until + // NetworkMonitor detects partial connectivity + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + mWiFiNetworkAgent.setNetworkValid(); + waitForIdle(); + try { + verify(mWiFiNetworkAgent.mNetworkMonitor, + timeout(TIMEOUT_MS).times(1)).notifyAcceptPartialConnectivity(); + } catch (RemoteException e) { + fail(e.getMessage()); + } + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + // Wifi should be the default network. + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + } + @Test public void testCaptivePortal() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();