When no Internet access is detected, display a notification first.

Previously, we displayed a dialog on top of whatever the user was
doing. Instead, display a notification that can take the user to
the dialog.

Because the notification is less intrusive, also enable it even
if the network without Internet access is already the system
default network. Previously we did not do this because in that
case the user might not have other useful options, and thus a
dialog would have been too intrusive.

Bug: 20081183
Bug: 20538448
Change-Id: I677be238d675c13a13dc0bae2dbb37cbe4f52cb6
This commit is contained in:
Lorenzo Colitti
2015-05-21 16:17:05 +09:00
parent 50e554808b
commit 74baf41e0f

View File

@@ -1997,20 +1997,22 @@ public class ConnectivityService extends IConnectivityManager.Stub
break; break;
} }
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
if (msg.arg1 == 0) { if (msg.arg1 == 0) {
setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null); setProvNotificationVisibleIntent(false, netId, null, 0, null, null);
} else { } else {
final NetworkAgentInfo nai; final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) { synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2); nai = mNetworkForNetId.get(netId);
} }
if (nai == null) { if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break; break;
} }
nai.captivePortalDetected = true; nai.captivePortalDetected = true;
setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(), setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj); nai.networkInfo.getType(),nai.networkInfo.getExtraInfo(),
(PendingIntent)msg.obj);
} }
break; break;
} }
@@ -2367,9 +2369,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
if (nai.everValidated) { if (nai.everValidated) {
// The network validated while the dialog box was up. Don't make any changes. There's a // The network validated while the dialog box was up. Take no action.
// TODO in the dialog code to make it go away if the network validates; once that's
// implemented, taking action here will be confusing.
return; return;
} }
@@ -2389,16 +2389,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0); NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
} }
// TODO: should we also disconnect from the network if accept is false? if (!accept) {
// Tell the NetworkAgent that the network does not have Internet access (because that's
// what we just told the user). This will hint to Wi-Fi not to autojoin this network in
// the future. We do this now because NetworkMonitor might not yet have finished
// validating and thus we might not yet have received an EVENT_NETWORK_TESTED.
nai.asyncChannel.sendMessage(NetworkAgent.CMD_REPORT_NETWORK_STATUS,
NetworkAgent.INVALID_NETWORK, 0, null);
// TODO: Tear the network down once we have determined how to tell WifiStateMachine not
// to reconnect to it immediately. http://b/20739299
}
} }
private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) { private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
if (DBG) log("scheduleUnvalidatedPrompt " + nai.network);
mHandler.sendMessageDelayed( mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network), mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
PROMPT_UNVALIDATED_DELAY_MS); PROMPT_UNVALIDATED_DELAY_MS);
} }
private void handlePromptUnvalidated(Network network) { private void handlePromptUnvalidated(Network network) {
if (DBG) log("handlePromptUnvalidated " + network);
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
// Only prompt if the network is unvalidated and was explicitly selected by the user, and if // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
@@ -2409,31 +2421,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
return; return;
} }
// TODO: What should we do if we've already switched to this network because we had no
// better option? There are two obvious alternatives.
//
// 1. Decide that there's no point prompting because this is our only usable network.
// However, because we didn't prompt, if later on a validated network comes along, we'll
// either a) silently switch to it - bad if the user wanted to connect to stay on this
// unvalidated network - or b) prompt the user at that later time - bad because the user
// might not understand why they are now being prompted.
//
// 2. Always prompt the user, even if we have no other network to use. The user could then
// try to find an alternative network to join (remember, if we got here, then the user
// selected this network manually). This is bad because the prompt isn't really very
// useful.
//
// For now we do #1, but we can revisit that later.
if (isDefaultNetwork(nai)) {
return;
}
Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED); Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network); intent.setData(Uri.fromParts("netId", Integer.toString(network.netId), null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("com.android.settings", intent.setClassName("com.android.settings",
"com.android.settings.wifi.WifiNoInternetDialog"); "com.android.settings.wifi.WifiNoInternetDialog");
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
setProvNotificationVisibleIntent(true, nai.network.netId, NotificationType.NO_INTERNET,
nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(), pendingIntent);
} }
private class InternalHandler extends Handler { private class InternalHandler extends Handler {
@@ -3200,7 +3197,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
private static final String NOTIFICATION_ID = "CaptivePortal.Notification"; private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
private volatile boolean mIsNotificationVisible = false; private static enum NotificationType { SIGN_IN, NO_INTERNET; };
private void setProvNotificationVisible(boolean visible, int networkType, String action) { private void setProvNotificationVisible(boolean visible, int networkType, String action) {
if (DBG) { if (DBG) {
@@ -3211,21 +3208,31 @@ public class ConnectivityService extends IConnectivityManager.Stub
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
// Concatenate the range of types onto the range of NetIDs. // Concatenate the range of types onto the range of NetIDs.
int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE); int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
setProvNotificationVisibleIntent(visible, id, networkType, null, pendingIntent); setProvNotificationVisibleIntent(visible, id, NotificationType.SIGN_IN,
networkType, null, pendingIntent);
} }
/** /**
* Show or hide network provisioning notificaitons. * Show or hide network provisioning notifications.
*
* We use notifications for two purposes: to notify that a network requires sign in
* (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
* (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
* particular network we can display the notification type that was most recently requested.
* So for example if a captive portal fails to reply within a few seconds of connecting, we
* might first display NO_INTERNET, and then when the captive portal check completes, display
* SIGN_IN.
* *
* @param id an identifier that uniquely identifies this notification. This must match * @param id an identifier that uniquely identifies this notification. This must match
* between show and hide calls. We use the NetID value but for legacy callers * between show and hide calls. We use the NetID value but for legacy callers
* we concatenate the range of types with the range of NetIDs. * we concatenate the range of types with the range of NetIDs.
*/ */
private void setProvNotificationVisibleIntent(boolean visible, int id, int networkType, private void setProvNotificationVisibleIntent(boolean visible, int id,
String extraInfo, PendingIntent intent) { NotificationType notifyType, int networkType, String extraInfo, PendingIntent intent) {
if (DBG) { if (DBG) {
log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" + log("setProvNotificationVisibleIntent " + notifyType + " visible=" + visible
networkType + " extraInfo=" + extraInfo); + " networkType=" + getNetworkTypeName(networkType)
+ " extraInfo=" + extraInfo);
} }
Resources r = Resources.getSystem(); Resources r = Resources.getSystem();
@@ -3237,6 +3244,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
CharSequence details; CharSequence details;
int icon; int icon;
Notification notification = new Notification(); Notification notification = new Notification();
if (notifyType == NotificationType.NO_INTERNET &&
networkType == ConnectivityManager.TYPE_WIFI) {
title = r.getString(R.string.wifi_no_internet, 0);
details = r.getString(R.string.wifi_no_internet_detailed);
icon = R.drawable.stat_notify_wifi_in_range; // TODO: Need new icon.
} else if (notifyType == NotificationType.SIGN_IN) {
switch (networkType) { switch (networkType) {
case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIFI:
title = r.getString(R.string.wifi_available_sign_in, 0); title = r.getString(R.string.wifi_available_sign_in, 0);
@@ -3259,6 +3272,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
icon = R.drawable.stat_notify_rssi_in_range; icon = R.drawable.stat_notify_rssi_in_range;
break; break;
} }
} else {
Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network type "
+ getNetworkTypeName(networkType));
return;
}
notification.when = 0; notification.when = 0;
notification.icon = icon; notification.icon = icon;
@@ -3272,18 +3290,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
try { try {
notificationManager.notify(NOTIFICATION_ID, id, notification); notificationManager.notify(NOTIFICATION_ID, id, notification);
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
loge("setNotificaitionVisible: visible notificationManager npe=" + npe); loge("setNotificationVisible: visible notificationManager npe=" + npe);
npe.printStackTrace(); npe.printStackTrace();
} }
} else { } else {
try { try {
notificationManager.cancel(NOTIFICATION_ID, id); notificationManager.cancel(NOTIFICATION_ID, id);
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
loge("setNotificaitionVisible: cancel notificationManager npe=" + npe); loge("setNotificationVisible: cancel notificationManager npe=" + npe);
npe.printStackTrace(); npe.printStackTrace();
} }
} }
mIsNotificationVisible = visible;
} }
/** Location to an updatable file listing carrier provisioning urls. /** Location to an updatable file listing carrier provisioning urls.