Check if network has partial connectivity

In some networks, network validation may only get success
result for http probe but fail result for https probe.
For this kind of network, it may still work at some websites
or apps, but user didn't know about that. In order to fix this
issue, we will check if network has partial connectivity and
notify user to make a choice if they want to use this partial
connectivity or not.

Bug: 113450764
Test: 1. Build pass.
      2. Fake partial connectivity case for testing.
      3. atest FrameworksNetTests
      4. atest NetworkStackTests

Change-Id: I69ed00ac4850904ff708c9fef22e148879a10e92
This commit is contained in:
lucaslin
2019-03-12 13:08:03 +08:00
committed by Lucas Lin
parent a0b880c9e8
commit 2240ef679d
9 changed files with 394 additions and 48 deletions

View File

@@ -426,6 +426,16 @@ public class ConnectivityManager {
public static final String ACTION_PROMPT_LOST_VALIDATION =
"android.net.conn.PROMPT_LOST_VALIDATION";
/**
* Action used to display a dialog that asks the user whether to stay connected to a network
* that has not validated. This intent is used to start the dialog in settings via
* startActivity.
*
* @hide
*/
public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY =
"android.net.conn.PROMPT_PARTIAL_CONNECTIVITY";
/**
* Invalid tethering type.
* @see #startTethering(int, boolean, OnStartTetheringCallback)
@@ -4018,7 +4028,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);
@@ -4027,6 +4037,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
@@ -4037,7 +4070,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);

View File

@@ -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);

View File

@@ -143,7 +143,8 @@ public final class NetworkCapabilities implements Parcelable {
NET_CAPABILITY_NOT_CONGESTED,
NET_CAPABILITY_NOT_SUSPENDED,
NET_CAPABILITY_OEM_PAID,
NET_CAPABILITY_MCX
NET_CAPABILITY_MCX,
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
})
public @interface NetCapability { }
@@ -304,8 +305,15 @@ public final class NetworkCapabilities implements Parcelable {
*/
public static final int NET_CAPABILITY_MCX = 23;
/**
* Indicates that this network was tested to only provide partial connectivity.
* @hide
*/
@SystemApi
public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_MCX;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -320,7 +328,8 @@ public final class NetworkCapabilities implements Parcelable {
| (1 << NET_CAPABILITY_NOT_ROAMING)
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_NOT_CONGESTED)
| (1 << NET_CAPABILITY_NOT_SUSPENDED);
| (1 << NET_CAPABILITY_NOT_SUSPENDED)
| (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -374,6 +383,15 @@ public final class NetworkCapabilities implements Parcelable {
(1 << NET_CAPABILITY_SUPL) |
(1 << NET_CAPABILITY_WIFI_P2P);
/**
* Capabilities that are managed by ConnectivityService.
*/
private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
(1 << NET_CAPABILITY_VALIDATED)
| (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Adds the given capability to this {@code NetworkCapability} instance.
* Multiple capabilities may be applied sequentially. Note that when searching
@@ -507,6 +525,14 @@ public final class NetworkCapabilities implements Parcelable {
&& ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
}
/**
* Check if this NetworkCapabilities has system managed capabilities or not.
* @hide
*/
public boolean hasConnectivityManagedCapability() {
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
/** Note this method may result in having the same capability in wanted and unwanted lists. */
private void combineNetCapabilities(NetworkCapabilities nc) {
this.mNetworkCapabilities |= nc.mNetworkCapabilities;
@@ -1599,31 +1625,32 @@ public final class NetworkCapabilities implements Parcelable {
*/
public static String capabilityNameOf(@NetCapability int capability) {
switch (capability) {
case NET_CAPABILITY_MMS: return "MMS";
case NET_CAPABILITY_SUPL: return "SUPL";
case NET_CAPABILITY_DUN: return "DUN";
case NET_CAPABILITY_FOTA: return "FOTA";
case NET_CAPABILITY_IMS: return "IMS";
case NET_CAPABILITY_CBS: return "CBS";
case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
case NET_CAPABILITY_IA: return "IA";
case NET_CAPABILITY_RCS: return "RCS";
case NET_CAPABILITY_XCAP: return "XCAP";
case NET_CAPABILITY_EIMS: return "EIMS";
case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
case NET_CAPABILITY_INTERNET: return "INTERNET";
case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
case NET_CAPABILITY_TRUSTED: return "TRUSTED";
case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
case NET_CAPABILITY_VALIDATED: return "VALIDATED";
case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
case NET_CAPABILITY_MCX: return "MCX";
default: return Integer.toString(capability);
case NET_CAPABILITY_MMS: return "MMS";
case NET_CAPABILITY_SUPL: return "SUPL";
case NET_CAPABILITY_DUN: return "DUN";
case NET_CAPABILITY_FOTA: return "FOTA";
case NET_CAPABILITY_IMS: return "IMS";
case NET_CAPABILITY_CBS: return "CBS";
case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
case NET_CAPABILITY_IA: return "IA";
case NET_CAPABILITY_RCS: return "RCS";
case NET_CAPABILITY_XCAP: return "XCAP";
case NET_CAPABILITY_EIMS: return "EIMS";
case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
case NET_CAPABILITY_INTERNET: return "INTERNET";
case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
case NET_CAPABILITY_TRUSTED: return "TRUSTED";
case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
case NET_CAPABILITY_VALIDATED: return "VALIDATED";
case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
case NET_CAPABILITY_MCX: return "MCX";
case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
default: return Integer.toString(capability);
}
}

View File

@@ -51,6 +51,12 @@ public class NetworkMisc implements Parcelable {
*/
public boolean acceptUnvalidated;
/**
* Whether the user explicitly set that this network should be validated even if presence of
* only partial internet connectivity.
*/
public boolean acceptPartialConnectivity;
/**
* Set to avoid surfacing the "Sign in to network" notification.
* if carrier receivers/apps are registered to handle the carrier-specific provisioning

View File

@@ -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;
@@ -488,6 +490,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.
@@ -2487,9 +2498,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);
@@ -2514,6 +2523,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: {
@@ -2531,6 +2548,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);
@@ -2539,6 +2573,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 : "";
@@ -2568,6 +2613,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
@@ -2606,7 +2654,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 {
@@ -3192,14 +3240,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));
}
@@ -3225,6 +3280,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);
}
@@ -3243,6 +3302,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) {
@@ -3413,6 +3514,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;
@@ -3435,22 +3539,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);
}
}
@@ -3541,6 +3659,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;
@@ -5465,6 +5589,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;
}

View File

@@ -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
@@ -592,6 +595,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() + "} "
@@ -604,6 +609,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
+ "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+ "partialConnectivity{" + partialConnectivity + "} "
+ "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+ "clat{" + clatd + "} "
+ "}";
}

View File

@@ -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;

View File

@@ -20,6 +20,7 @@ import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
@@ -27,6 +28,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
@@ -334,6 +336,24 @@ public class NetworkCapabilitiesTest {
assertTrue(request.satisfiedByNetworkCapabilities(network));
}
@Test
public void testConnectivityManagedCapabilities() {
NetworkCapabilities nc = new NetworkCapabilities();
assertFalse(nc.hasConnectivityManagedCapability());
// Check every single system managed capability.
nc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
assertTrue(nc.hasConnectivityManagedCapability());
nc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
nc.addCapability(NET_CAPABILITY_FOREGROUND);
assertTrue(nc.hasConnectivityManagedCapability());
nc.removeCapability(NET_CAPABILITY_FOREGROUND);
nc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
assertTrue(nc.hasConnectivityManagedCapability());
nc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
nc.addCapability(NET_CAPABILITY_VALIDATED);
assertTrue(nc.hasConnectivityManagedCapability());
}
@Test
public void testEqualsNetCapabilities() {
NetworkCapabilities nc1 = new NetworkCapabilities();

View File

@@ -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;
@@ -442,6 +444,11 @@ public class ConnectivityServiceTest {
mNmValidationRedirectUrl = redirectUrl;
}
void setNetworkPartial() {
mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
mNmValidationRedirectUrl = null;
}
MockNetworkAgent(int transport) {
this(transport, new LinkProperties());
}
@@ -484,6 +491,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());
}
@@ -669,6 +677,11 @@ public class ConnectivityServiceTest {
connect(false);
}
public void connectWithPartialConnectivity() {
setNetworkPartial();
connect(false);
}
public void suspend() {
mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -2497,6 +2510,106 @@ public class ConnectivityServiceTest {
verifyActiveNetwork(TRANSPORT_CELLULAR);
}
@Test
public void testPartialConnectivity() {
// Register network callback.
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
.build();
TestNetworkCallback callback = new TestNetworkCallback();
mCm.registerNetworkCallback(request, callback);
// Bring up validated mobile data.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
// Bring up wifi with partial connectivity.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connectWithPartialConnectivity();
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
// Mobile data should be the default network.
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.assertNoCallback();
// If the user chooses yes to use this partial connectivity wifi, switch the default
// network to wifi and check if wifi becomes valid or not.
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
false /* always */);
// With https probe disabled, NetworkMonitor should pass the network validation with http
// probe.
mWiFiNetworkAgent.setNetworkValid();
waitForIdle();
try {
verify(mWiFiNetworkAgent.mNetworkMonitor,
timeout(TIMEOUT_MS).times(1)).notifyAcceptPartialConnectivity();
} catch (RemoteException e) {
fail(e.getMessage());
}
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED,
mWiFiNetworkAgent);
assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Disconnect and reconnect wifi with partial connectivity again.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connectWithPartialConnectivity();
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
// Mobile data should be the default network.
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// If the user chooses no, disconnect wifi immediately.
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */,
false /* always */);
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// If user accepted partial connectivity before, and device reconnects to that network
// again, but now the network has full connectivity. The network shouldn't contain
// NET_CAPABILITY_PARTIAL_CONNECTIVITY.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
// acceptUnvalidated is also used as setting for accepting partial networks.
mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */);
mWiFiNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
// Wifi should be the default network.
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// If user accepted partial connectivity before, and now the device reconnects to the
// partial connectivity network. The network should be valid and contain
// NET_CAPABILITY_PARTIAL_CONNECTIVITY.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */);
mWiFiNetworkAgent.connectWithPartialConnectivity();
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: If the user accepted partial connectivity, we shouldn't switch to wifi until
// NetworkMonitor detects partial connectivity
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.setNetworkValid();
waitForIdle();
try {
verify(mWiFiNetworkAgent.mNetworkMonitor,
timeout(TIMEOUT_MS).times(1)).notifyAcceptPartialConnectivity();
} catch (RemoteException e) {
fail(e.getMessage());
}
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
// Wifi should be the default network.
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
}
@Test
public void testCaptivePortal() {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();