diff options
20 files changed, 510 insertions, 53 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 04bddc1f6dc5..b2030ec8a911 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4155,6 +4155,7 @@ package android.net { method public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities); field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 + field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 } public class NetworkKey implements android.os.Parcelable { @@ -4303,10 +4304,12 @@ package android.net.captiveportal { ctor public CaptivePortalProbeResult(int, String, String); ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); method public boolean isFailed(); + method public boolean isPartialConnectivity(); method public boolean isPortal(); method public boolean isSuccessful(); field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; field public static final int FAILED_CODE = 599; // 0x257 + field public static final android.net.captiveportal.CaptivePortalProbeResult PARTIAL; field public static final int PORTAL_CODE = 302; // 0x12e field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; field public static final int SUCCESS_CODE = 204; // 0xcc @@ -4438,6 +4441,7 @@ package android.net.metrics { field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8 field public static final int NETWORK_LINGER = 5; // 0x5 + field public static final int NETWORK_PARTIAL_CONNECTIVITY = 13; // 0xd field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9 field public static final int NETWORK_UNLINGER = 6; // 0x6 diff --git a/api/test-current.txt b/api/test-current.txt index 97f5ffe6c849..46e061b740e6 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1276,10 +1276,12 @@ package android.net.captiveportal { ctor public CaptivePortalProbeResult(int, String, String); ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); method public boolean isFailed(); + method public boolean isPartialConnectivity(); method public boolean isPortal(); method public boolean isSuccessful(); field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; field public static final int FAILED_CODE = 599; // 0x257 + field public static final android.net.captiveportal.CaptivePortalProbeResult PARTIAL; field public static final int PORTAL_CODE = 302; // 0x12e field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; field public static final int SUCCESS_CODE = 204; // 0xcc @@ -1411,6 +1413,7 @@ package android.net.metrics { field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8 field public static final int NETWORK_LINGER = 5; // 0x5 + field public static final int NETWORK_PARTIAL_CONNECTIVITY = 13; // 0xd field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9 field public static final int NETWORK_UNLINGER = 6; // 0x6 diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fb0821e735e9..3f8410f0cc7f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -427,6 +427,16 @@ public class ConnectivityManager { "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) * @hide @@ -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); @@ -4044,6 +4054,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 * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code @@ -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 a425a91ef39f..403b44d6d7d7 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/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl index c94cdde15aa8..5d1ab983c5fc 100644 --- a/core/java/android/net/INetworkMonitor.aidl +++ b/core/java/android/net/INetworkMonitor.aidl @@ -32,9 +32,16 @@ oneway interface INetworkMonitor { // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). const int NETWORK_TEST_RESULT_INVALID = 1; + // After a network has been tested, this result can be sent with EVENT_NETWORK_TESTED. + // The network may be used as a default internet connection, but it was found to be a partial + // connectivity network which can get the pass result for http probe but get the failed result + // for https probe. + const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2; + void start(); void launchCaptivePortalApp(); void notifyCaptivePortalAppFinished(int response); + void notifyAcceptPartialConnectivity(); void forceReevaluation(int uid); void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config); void notifyDnsResponse(int returnCode); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index eb0fe33df03a..c57ae0c9fc32 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 @@ -375,6 +384,15 @@ public final class NetworkCapabilities implements Parcelable { (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 * for a network to satisfy a request, all capabilities requested must be satisfied. @@ -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 ed0b61efd701..9ba3bd940a96 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkMisc.java @@ -52,6 +52,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 * procedure, a carrier specific provisioning notification will be placed. diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java index 7432687e136f..3930344e5d27 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java @@ -30,10 +30,20 @@ public final class CaptivePortalProbeResult { public static final int SUCCESS_CODE = 204; public static final int FAILED_CODE = 599; public static final int PORTAL_CODE = 302; + // Set partial connectivity http response code to -1 to prevent conflict with the other http + // response codes. Besides the default http response code of probe result is set as 599 in + // NetworkMonitor#sendParallelHttpProbes(), so response code will be set as -1 only when + // NetworkMonitor detects partial connectivity. + /** + * @hide + */ + public static final int PARTIAL_CODE = -1; public static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE); public static final CaptivePortalProbeResult SUCCESS = new CaptivePortalProbeResult(SUCCESS_CODE); + public static final CaptivePortalProbeResult PARTIAL = + new CaptivePortalProbeResult(PARTIAL_CODE); private final int mHttpResponseCode; // HTTP response code returned from Internet probe. public final String redirectUrl; // Redirect destination returned from Internet probe. @@ -69,4 +79,8 @@ public final class CaptivePortalProbeResult { public boolean isFailed() { return !isSuccessful() && !isPortal(); } + + public boolean isPartialConnectivity() { + return mHttpResponseCode == PARTIAL_CODE; + } } diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java index ed5884378cc5..bed914d6b2ab 100644 --- a/core/java/android/net/metrics/NetworkEvent.java +++ b/core/java/android/net/metrics/NetworkEvent.java @@ -50,6 +50,8 @@ public final class NetworkEvent implements IpConnectivityLog.Event { public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; + public static final int NETWORK_PARTIAL_CONNECTIVITY = 13; + /** @hide */ @IntDef(value = { NETWORK_CONNECTED, @@ -64,6 +66,7 @@ public final class NetworkEvent implements IpConnectivityLog.Event { NETWORK_FIRST_VALIDATION_PORTAL_FOUND, NETWORK_REVALIDATION_PORTAL_FOUND, NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND, + NETWORK_PARTIAL_CONNECTIVITY, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 05cf419f42b0..f7b27f0c4d07 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3401,13 +3401,18 @@ <string name="network_available_sign_in_detailed"><xliff:g id="network_ssid">%1$s</xliff:g></string> <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's title. --> - <string name="wifi_no_internet">Wi-Fi has no internet access</string> + <string name="wifi_no_internet"><xliff:g id="network_ssid" example="GoogleGuest">%1$s</xliff:g> has no internet access</string> <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. --> <string name="wifi_no_internet_detailed">Tap for options</string> <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] --> <string name="captive_portal_logged_in_detailed">Connected</string> + <!-- A notification is shown when the user connects to a network that doesn't have access to some services (e.g. Push notifications may not work). This is the notification's title. [CHAR LIMIT=50] --> + <string name="network_partial_connectivity"><xliff:g id="network_ssid" example="GoogleGuest">%1$s</xliff:g> has limited connectivity</string> + + <!-- A notification is shown when the user connects to a network that doesn't have access to some services (e.g. Push notifications may not work). This is the notification's message. [CHAR LIMIT=50] --> + <string name="network_partial_connectivity_detailed">Tap to connect anyway</string> <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notifications's title. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c7e19c559701..69def5b70f07 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1244,6 +1244,8 @@ <java-symbol type="string" name="mediasize_japanese_kahu" /> <java-symbol type="string" name="mediasize_japanese_kaku2" /> <java-symbol type="string" name="mediasize_japanese_you4" /> + <java-symbol type="string" name="network_partial_connectivity" /> + <java-symbol type="string" name="network_partial_connectivity_detailed" /> <java-symbol type="string" name="reason_service_unavailable" /> <java-symbol type="string" name="reason_unknown" /> <java-symbol type="string" name="restr_pin_enter_admin_pin" /> diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 90db207c9902..72955bb22a00 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -253,6 +253,12 @@ public class NetworkStackService extends Service { } @Override + public void notifyAcceptPartialConnectivity() { + checkNetworkStackCallingPermission(); + mNm.notifyAcceptPartialConnectivity(); + } + + @Override public void forceReevaluation(int uid) { checkNetworkStackCallingPermission(); mNm.forceReevaluation(uid); diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index c3447fdb3d78..fc56da7eacd8 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -24,6 +24,7 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; import static android.net.ConnectivityManager.TYPE_MOBILE; 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.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; @@ -227,6 +228,12 @@ public class NetworkMonitor extends StateMachine { */ public static final int EVENT_DNS_NOTIFICATION = 17; + /** + * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and + * NetworkMonitor should ignore the https probe. + */ + public static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000; @@ -379,6 +386,14 @@ public class NetworkMonitor extends StateMachine { } /** + * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and + * NetworkMonitor should ignore the https probe. + */ + public void notifyAcceptPartialConnectivity() { + sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY); + } + + /** * Request the NetworkMonitor to reevaluate the network. */ public void forceReevaluation(int responsibleUid) { @@ -636,6 +651,10 @@ public class NetworkMonitor extends StateMachine { case EVENT_DNS_NOTIFICATION: mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); break; + case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: + mUseHttps = false; + transitionTo(mEvaluatingPrivateDnsState); + break; default: break; } @@ -1058,6 +1077,11 @@ public class NetworkMonitor extends StateMachine { notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl); mLastPortalProbeResult = probeResult; transitionTo(mCaptivePortalState); + } else if (probeResult.isPartialConnectivity()) { + logNetworkEvent(NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY); + notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY, + probeResult.redirectUrl); + transitionTo(mWaitingForNextProbeState); } else { logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl); @@ -1065,7 +1089,8 @@ public class NetworkMonitor extends StateMachine { } return HANDLED; case EVENT_DNS_NOTIFICATION: - // Leave the event to DefaultState to record correct dns timestamp. + case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: + // Leave the event to DefaultState. return NOT_HANDLED; default: // Wait for probe result and defer events to next state by default. @@ -1504,10 +1529,11 @@ public class NetworkMonitor extends StateMachine { // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. final CaptivePortalProbeSpec probeSpec = nextFallbackSpec(); final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl(); + CaptivePortalProbeResult fallbackProbeResult = null; if (fallbackUrl != null) { - CaptivePortalProbeResult result = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec); - if (result.isPortal()) { - return result; + fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec); + if (fallbackProbeResult.isPortal()) { + return fallbackProbeResult; } } // Otherwise wait until http and https probes completes and use their results. @@ -1517,6 +1543,12 @@ public class NetworkMonitor extends StateMachine { return httpProbe.result(); } httpsProbe.join(); + final boolean isHttpSuccessful = + (httpProbe.result().isSuccessful() + || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful())); + if (httpsProbe.result().isFailed() && isHttpSuccessful) { + return CaptivePortalProbeResult.PARTIAL; + } return httpsProbe.result(); } catch (InterruptedException e) { validationLog("Error: http or https probe wait interrupted!"); diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index 34ca6ac2ac20..d93aef2b73f6 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import static android.net.CaptivePortal.APP_RETURN_DISMISSED; 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_INTERNET; import static android.provider.Settings.Global.DATA_STALL_EVALUATION_TYPE_DNS; @@ -572,6 +573,34 @@ public class NetworkMonitorTest { stats.build()); } + @Test + public void testIgnoreHttpsProbe() throws Exception { + setSslException(mHttpsConnection); + setStatus(mHttpConnection, 204); + + final NetworkMonitor nm = makeMonitor(); + nm.notifyNetworkConnected(); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY, null); + + nm.notifyAcceptPartialConnectivity(); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + } + + @Test + public void testIsPartialConnectivity() throws IOException { + setStatus(mHttpsConnection, 500); + setStatus(mHttpConnection, 204); + setStatus(mFallbackConnection, 500); + assertPartialConnectivity(makeMonitor().isCaptivePortal()); + + setStatus(mHttpsConnection, 500); + setStatus(mHttpConnection, 500); + setStatus(mFallbackConnection, 204); + assertPartialConnectivity(makeMonitor().isCaptivePortal()); + } + private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) { for (int i = 0; i < count; i++) { wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount( @@ -649,6 +678,10 @@ public class NetworkMonitorTest { assertFalse(result.isSuccessful()); } + private void assertPartialConnectivity(CaptivePortalProbeResult result) { + assertTrue(result.isPartialConnectivity()); + } + private void setSslException(HttpURLConnection connection) throws IOException { doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode(); } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 3a8931630338..16d8e95f4845 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -243,6 +243,8 @@ message SystemMessage { NOTE_NETWORK_SWITCH = 743; // Device logged-in captive portal network successfully NOTE_NETWORK_LOGGED_IN = 744; + // A partial connectivity network was detected during network validation + NOTE_NETWORK_PARTIAL_CONNECTIVITY = 745; // Notify the user that their work profile has been deleted // Package: android diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 199477ce26c9..72f7a68ad2ad 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; @@ -490,6 +492,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 6ef9fbbf0da8..8f2825ca72d8 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<NetworkAgentInfo> { // 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<NetworkAgentInfo> { 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<NetworkAgentInfo> { + "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 b50477bc120f..053da0d5b1e2 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 4d4915b83ca4..ad76388b3c9b 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; @@ -335,6 +337,24 @@ public class NetworkCapabilitiesTest { } @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(); NetworkCapabilities nc2 = new NetworkCapabilities(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index d29234fbf171..985d66739d31 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); @@ -2537,6 +2550,106 @@ public class ConnectivityServiceTest { } @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(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |