Merge "Check if network has partial connectivity"
diff --git a/api/system-current.txt b/api/system-current.txt
index 1d6b595..b89a1da 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3193,6 +3193,7 @@
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 {
@@ -3341,10 +3342,12 @@
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
@@ -3476,6 +3479,7 @@
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 0550663..d43b1c4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -751,10 +751,12 @@
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
@@ -886,6 +888,7 @@
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 249357e..91fefa2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -427,6 +427,16 @@
"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
@@ -4018,7 +4028,7 @@
*
* @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);
@@ -4028,6 +4038,29 @@
}
/**
+ * 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
@@ -4037,7 +4070,7 @@
*
* @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 9fc56b4..2df4e75 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -176,6 +176,7 @@
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 c94cdde..5d1ab98 100644
--- a/core/java/android/net/INetworkMonitor.aidl
+++ b/core/java/android/net/INetworkMonitor.aidl
@@ -32,9 +32,16 @@
// 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 0a63e75..e1cfe99 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -143,7 +143,8 @@
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 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 @@
| (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 @@
(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 @@
&& ((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 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 c0487b5..6fb2390 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -52,6 +52,12 @@
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 7432687..3930344 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -30,10 +30,20 @@
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 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 f5b2ff1..5128115 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -50,6 +50,8 @@
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 @@
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 b412f0f..b4e69ad 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3197,13 +3197,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 4689caa..a95fa54 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1211,6 +1211,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 90db207..72955bb 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -253,6 +253,12 @@
}
@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 c3447fd..fc56da7 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.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 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
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 @@
// 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 @@
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 34ca6ac..d93aef2 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 @@
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 @@
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 @@
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 8ee55e1..b94600b 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -233,6 +233,8 @@
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 39da90f..219cf33 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_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_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 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.
*/
@@ -2487,9 +2498,7 @@
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);
@@ -2514,6 +2523,14 @@
}
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: {
@@ -2531,6 +2548,23 @@
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);
@@ -2539,6 +2573,17 @@
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 : "";
@@ -2568,6 +2613,9 @@
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
@@ -2606,7 +2654,7 @@
}
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 {
@@ -3192,14 +3240,21 @@
@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));
}
@@ -3225,6 +3280,10 @@
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);
}
@@ -3243,6 +3302,48 @@
}
+ 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) {
@@ -3413,6 +3514,9 @@
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;
@@ -3435,22 +3539,36 @@
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);
}
}
@@ -3541,6 +3659,12 @@
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;
@@ -5465,6 +5589,11 @@
} 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 f11a0de..65eb158 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 @@
// 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
@@ -592,6 +595,8 @@
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() + "} "
@@ -604,6 +609,8 @@
+ "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 b50477b..053da0d 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 @@
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 @@
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 @@
}
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 4d4915b..ad76388 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.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_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 @@
}
@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 9453a80..70eb70b 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_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_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;
@@ -442,6 +444,11 @@
mNmValidationRedirectUrl = redirectUrl;
}
+ void setNetworkPartial() {
+ mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
+ mNmValidationRedirectUrl = null;
+ }
+
MockNetworkAgent(int transport) {
this(transport, new LinkProperties());
}
@@ -484,6 +491,7 @@
try {
doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected();
doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+ doAnswer(validateAnswer).when(mNetworkMonitor).notifyAcceptPartialConnectivity();
} catch (RemoteException e) {
fail(e.getMessage());
}
@@ -669,6 +677,11 @@
connect(false);
}
+ public void connectWithPartialConnectivity() {
+ setNetworkPartial();
+ connect(false);
+ }
+
public void suspend() {
mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -2498,6 +2511,106 @@
}
@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()