Popup a notification after logging in the captive portal network

Captive portal app will be auto dismissed after user login the
captive portal network. In order to improve the user experience,
popup a notification to notify user that the captive portal
network is connected.

Bug: 113629026
Test: 1.atest FrameworksNetTests:NetworkNotificationManagerTest
      2.Connect to a captive portal network and login, check if
      there is a notification popup.

Change-Id: Id54d12268e107af2f213c2bb348c5f7908e880f4
This commit is contained in:
lucaslin
2019-01-24 15:55:30 +08:00
parent 4bcdcfebd4
commit b1e8e385dd
4 changed files with 151 additions and 38 deletions

View File

@@ -97,10 +97,10 @@ import android.net.VpnService;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent; import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage; import android.net.netlink.InetDiagMessage;
import android.net.shared.NetdService;
import android.net.shared.NetworkMonitorUtils; import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig; import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker; import android.net.util.MultinetworkPolicyTracker;
import android.net.shared.NetdService;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@@ -237,6 +237,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// connect anyway?" dialog after the user selects a network that doesn't validate. // connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
// How long to dismiss network notification.
private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
// Default to 30s linger time-out. Modifiable only for testing. // Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000; private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -472,6 +475,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/ */
public static final int EVENT_PROVISIONING_NOTIFICATION = 43; public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
/**
* This event can handle dismissing notification by given network id.
*/
public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
/** /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown. * should be shown.
@@ -2476,6 +2484,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID); final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated; final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai); final boolean wasDefault = isDefaultNetwork(nai);
if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
&& valid) {
nai.captivePortalLoginNotified = true;
showNetworkNotification(nai, NotificationType.LOGGED_IN);
}
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
@@ -2496,7 +2509,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateCapabilities(oldScore, nai, nai.networkCapabilities); updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566 // If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) handleFreshlyValidatedNetwork(nai); if (valid) {
handleFreshlyValidatedNetwork(nai);
// Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
// valid.
mNotifier.clearNotification(nai.network.netId,
NotificationType.NO_INTERNET);
mNotifier.clearNotification(nai.network.netId,
NotificationType.LOST_INTERNET);
}
} }
updateInetCondition(nai); updateInetCondition(nai);
// Let the NetworkAgent know the state of its network // Let the NetworkAgent know the state of its network
@@ -2520,6 +2541,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
final int oldScore = nai.getCurrentScore(); final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible; nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible; nai.everCaptivePortalDetected |= visible;
if (visible) {
nai.captivePortalLoginNotified = false;
}
if (nai.lastCaptivePortalDetected && if (nai.lastCaptivePortalDetected &&
Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) { Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.name()); if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2531,7 +2555,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateCapabilities(oldScore, nai, nai.networkCapabilities); updateCapabilities(oldScore, nai, nai.networkCapabilities);
} }
if (!visible) { if (!visible) {
mNotifier.clearNotification(netId); // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
// notifications belong to the same network may be cleared unexpected.
mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else { } else {
if (nai == null) { if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -3237,9 +3264,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
pw.decreaseIndent(); pw.decreaseIndent();
} }
private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) { private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action; final String action;
switch (type) { switch (type) {
case LOGGED_IN:
action = Settings.ACTION_WIFI_SETTINGS;
mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
break;
case NO_INTERNET: case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED; action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
break; break;
@@ -3252,10 +3285,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
Intent intent = new Intent(action); Intent intent = new Intent(action);
intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); if (type != NotificationType.LOGGED_IN) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
intent.setClassName("com.android.settings", intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
"com.android.settings.wifi.WifiNoInternetDialog"); intent.setClassName("com.android.settings",
"com.android.settings.wifi.WifiNoInternetDialog");
}
PendingIntent pendingIntent = PendingIntent.getActivityAsUser( PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3273,7 +3308,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return; return;
} }
showValidationNotification(nai, NotificationType.NO_INTERNET); showNetworkNotification(nai, NotificationType.NO_INTERNET);
} }
private void handleNetworkUnvalidated(NetworkAgentInfo nai) { private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3282,7 +3317,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) { mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
showValidationNotification(nai, NotificationType.LOST_INTERNET); showNetworkNotification(nai, NotificationType.LOST_INTERNET);
} }
} }
@@ -3428,6 +3463,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
case EVENT_DATA_SAVER_CHANGED: case EVENT_DATA_SAVER_CHANGED:
handleRestrictBackgroundChanged(toBool(msg.arg1)); handleRestrictBackgroundChanged(toBool(msg.arg1));
break; break;
case EVENT_TIMEOUT_NOTIFICATION:
mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
break;
} }
} }
} }

View File

@@ -152,6 +152,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Whether a captive portal was found during the last network validation attempt. // Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected; public boolean lastCaptivePortalDetected;
// Indicates the user was notified of a successful captive portal login since a portal was
// last detected.
public boolean captivePortalLoginNotified;
// Networks are lingered when they become unneeded as a result of their NetworkRequests being // 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 // 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 // network is taken down. This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
} }
public String toString() { public String toString() {
return "NetworkAgentInfo{ ni{" + networkInfo + "} " + return "NetworkAgentInfo{ ni{" + networkInfo + "} "
"network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
"lp{" + linkProperties + "} " + + "lp{" + linkProperties + "} "
"nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
"everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
"created{" + created + "} lingering{" + isLingering() + "} " + + "created{" + created + "} lingering{" + isLingering() + "} "
"explicitlySelected{" + networkMisc.explicitlySelected + "} " + + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
"acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
"everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
"lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
"clat{" + clatd + "} " + + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
"}"; + "clat{" + clatd + "} "
+ "}";
} }
public String name() { public String name() {

View File

@@ -16,13 +16,16 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.os.UserHandle; import android.os.UserHandle;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@ import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.widget.Toast; import android.widget.Toast;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.notification.SystemNotificationChannels;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
public class NetworkNotificationManager { public class NetworkNotificationManager {
@@ -47,7 +47,8 @@ public class NetworkNotificationManager {
LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET), LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH), NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET), NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN); SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
public final int eventId; public final int eventId;
@@ -192,6 +193,9 @@ public class NetworkNotificationManager {
details = r.getString(R.string.network_available_sign_in_detailed, name); details = r.getString(R.string.network_available_sign_in_detailed, name);
break; break;
} }
} else if (notifyType == NotificationType.LOGGED_IN) {
title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
details = r.getString(R.string.captive_portal_logged_in_detailed);
} else if (notifyType == NotificationType.NETWORK_SWITCH) { } else if (notifyType == NotificationType.NETWORK_SWITCH) {
String fromTransport = getTransportName(transportType); String fromTransport = getTransportName(transportType);
String toTransport = getTransportName(getFirstTransportType(switchToNai)); String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@ public class NetworkNotificationManager {
} }
} }
/**
* Clear the notification with the given id, only if it matches the given type.
*/
public void clearNotification(int id, NotificationType notifyType) {
final int previousEventId = mNotificationTypeMap.get(id);
final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
if (notifyType != previousNotifyType) {
return;
}
clearNotification(id);
}
public void clearNotification(int id) { public void clearNotification(int id) {
if (mNotificationTypeMap.indexOfKey(id) < 0) { if (mNotificationTypeMap.indexOfKey(id) < 0) {
return; return;
@@ -290,6 +306,10 @@ public class NetworkNotificationManager {
return (t != null) ? t.name() : "UNKNOWN"; return (t != null) ? t.name() : "UNKNOWN";
} }
/**
* A notification with a higher number will take priority over a notification with a lower
* number.
*/
private static int priority(NotificationType t) { private static int priority(NotificationType t) {
if (t == null) { if (t == null) {
return 0; return 0;
@@ -302,6 +322,7 @@ public class NetworkNotificationManager {
case NETWORK_SWITCH: case NETWORK_SWITCH:
return 2; return 2;
case LOST_INTERNET: case LOST_INTERNET:
case LOGGED_IN:
return 1; return 1;
default: default:
return 0; return 0;

View File

@@ -17,6 +17,7 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
@@ -34,26 +35,24 @@ import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class NetworkNotificationManagerTest { public class NetworkNotificationManagerTest {
@@ -194,4 +193,54 @@ public class NetworkNotificationManagerTest {
mManager.clearNotification(id); mManager.clearNotification(id);
verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any()); verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
} }
@Test
public void testSameLevelNotifications() {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
verify(mNotificationManager, times(1))
.notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
verify(mNotificationManager, times(1))
.notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
}
@Test
public void testClearNotificationByType() {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
// clearNotification(int id, NotificationType notifyType) will check if given type is equal
// to previous type or not. If they are equal then clear the notification; if they are not
// equal then return.
mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
verify(mNotificationManager, times(1))
.notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
// Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
// should be cleared.
mManager.clearNotification(id, LOGGED_IN);
verify(mNotificationManager, times(1))
.cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
verify(mNotificationManager, times(2))
.notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
// LOST_INTERNET notification popup after LOGGED_IN notification.
mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
verify(mNotificationManager, times(1))
.notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
// Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
// shouldn't be cleared.
mManager.clearNotification(id, LOGGED_IN);
// LOST_INTERNET shouldn't be cleared.
verify(mNotificationManager, never())
.cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
}
} }