blob: 68fe5053b58519f2469828bd80765840ddc1341c [file] [log] [blame]
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +09001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.Context;
Lorenzo Colitti0b599062016-08-22 22:36:19 +090023import android.content.Intent;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090024import android.content.res.Resources;
Lorenzo Colitti0b599062016-08-22 22:36:19 +090025import android.net.NetworkCapabilities;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090026import android.os.UserHandle;
27import android.telephony.TelephonyManager;
28import android.util.Slog;
Hugo Benichifb2609d2016-12-08 09:36:52 +090029import android.util.SparseArray;
30import android.util.SparseIntArray;
31import android.widget.Toast;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090032import com.android.internal.R;
Hugo Benichifb2609d2016-12-08 09:36:52 +090033import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090035
Hugo Benichifb2609d2016-12-08 09:36:52 +090036import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
37import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
38import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090039
40public class NetworkNotificationManager {
41
Hugo Benichifb2609d2016-12-08 09:36:52 +090042 public static enum NotificationType {
43 LOST_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_LOST_INTERNET),
44 NETWORK_SWITCH(MetricsEvent.NOTIFICATION_NETWORK_SWITCH),
45 NO_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_NO_INTERNET),
46 SIGN_IN(MetricsEvent.NOTIFICATION_NETWORK_SIGN_IN);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090047
Hugo Benichifb2609d2016-12-08 09:36:52 +090048 public final int eventId;
49
50 NotificationType(int eventId) {
51 this.eventId = eventId;
52 Holder.sIdToTypeMap.put(eventId, this);
53 }
54
55 private static class Holder {
56 private static SparseArray<NotificationType> sIdToTypeMap = new SparseArray<>();
57 }
58
59 public static NotificationType getFromId(int id) {
60 return Holder.sIdToTypeMap.get(id);
61 }
62 };
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090063
64 private static final String TAG = NetworkNotificationManager.class.getSimpleName();
65 private static final boolean DBG = true;
66 private static final boolean VDBG = false;
67
68 private final Context mContext;
69 private final TelephonyManager mTelephonyManager;
Lorenzo Colitti0b599062016-08-22 22:36:19 +090070 private final NotificationManager mNotificationManager;
Hugo Benichifb2609d2016-12-08 09:36:52 +090071 // Tracks the types of notifications managed by this instance, from creation to cancellation.
72 private final SparseIntArray mNotificationTypeMap;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090073
Lorenzo Colitti0b599062016-08-22 22:36:19 +090074 public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) {
75 mContext = c;
76 mTelephonyManager = t;
77 mNotificationManager = n;
Hugo Benichifb2609d2016-12-08 09:36:52 +090078 mNotificationTypeMap = new SparseIntArray();
Lorenzo Colitti0b599062016-08-22 22:36:19 +090079 }
80
81 // TODO: deal more gracefully with multi-transport networks.
82 private static int getFirstTransportType(NetworkAgentInfo nai) {
83 for (int i = 0; i < 64; i++) {
84 if (nai.networkCapabilities.hasTransport(i)) return i;
85 }
86 return -1;
87 }
88
89 private static String getTransportName(int transportType) {
90 Resources r = Resources.getSystem();
91 String[] networkTypes = r.getStringArray(R.array.network_switch_type_name);
92 try {
93 return networkTypes[transportType];
94 } catch (IndexOutOfBoundsException e) {
95 return r.getString(R.string.network_switch_type_name_unknown);
96 }
97 }
98
99 private static int getIcon(int transportType) {
100 return (transportType == TRANSPORT_WIFI) ?
101 R.drawable.stat_notify_wifi_in_range : // TODO: Distinguish ! from ?.
102 R.drawable.stat_notify_rssi_in_range;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900103 }
104
105 /**
106 * Show or hide network provisioning notifications.
107 *
108 * We use notifications for two purposes: to notify that a network requires sign in
109 * (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
110 * (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
111 * particular network we can display the notification type that was most recently requested.
112 * So for example if a captive portal fails to reply within a few seconds of connecting, we
113 * might first display NO_INTERNET, and then when the captive portal check completes, display
114 * SIGN_IN.
115 *
116 * @param id an identifier that uniquely identifies this notification. This must match
117 * between show and hide calls. We use the NetID value but for legacy callers
118 * we concatenate the range of types with the range of NetIDs.
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900119 * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
120 * or LOST_INTERNET notification, this is the network we're connecting to. For a
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900121 * NETWORK_SWITCH notification it's the network that we switched from. When this network
122 * disconnects the notification is removed.
123 * @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null
124 * in all other cases. Only used to determine the text of the notification.
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900125 */
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900126 public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai,
127 NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900128 final String tag = tagFor(id);
129 final int eventId = notifyType.eventId;
130 final int transportType;
131 final String extraInfo;
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900132 if (nai != null) {
133 transportType = getFirstTransportType(nai);
134 extraInfo = nai.networkInfo.getExtraInfo();
135 // Only notify for Internet-capable networks.
136 if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
137 } else {
138 // Legacy notifications.
139 transportType = TRANSPORT_CELLULAR;
140 extraInfo = null;
141 }
142
143 if (DBG) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900144 Slog.d(TAG, String.format(
Joe LaPennae1406162016-12-19 10:31:14 -0800145 "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
Paul Stewart835cb492016-12-19 08:45:32 -0800146 tag, nameOf(eventId), getTransportName(transportType), extraInfo,
147 highPriority));
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900148 }
149
150 Resources r = Resources.getSystem();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900151 CharSequence title;
152 CharSequence details;
153 int icon = getIcon(transportType);
154 if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
155 title = r.getString(R.string.wifi_no_internet, 0);
156 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900157 } else if (notifyType == NotificationType.LOST_INTERNET &&
158 transportType == TRANSPORT_WIFI) {
159 title = r.getString(R.string.wifi_no_internet, 0);
160 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900161 } else if (notifyType == NotificationType.SIGN_IN) {
162 switch (transportType) {
163 case TRANSPORT_WIFI:
164 title = r.getString(R.string.wifi_available_sign_in, 0);
165 details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
166 break;
167 case TRANSPORT_CELLULAR:
168 title = r.getString(R.string.network_available_sign_in, 0);
169 // TODO: Change this to pull from NetworkInfo once a printable
170 // name has been added to it
171 details = mTelephonyManager.getNetworkOperatorName();
172 break;
173 default:
174 title = r.getString(R.string.network_available_sign_in, 0);
175 details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
176 break;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900177 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900178 } else if (notifyType == NotificationType.NETWORK_SWITCH) {
179 String fromTransport = getTransportName(transportType);
180 String toTransport = getTransportName(getFirstTransportType(switchToNai));
181 title = r.getString(R.string.network_switch_metered, toTransport);
182 details = r.getString(R.string.network_switch_metered_detail, toTransport,
183 fromTransport);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900184 } else {
Hugo Benichia5bf8192016-12-20 09:57:43 +0900185 Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900186 + getTransportName(transportType));
187 return;
188 }
189
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900190 Notification.Builder builder = new Notification.Builder(mContext)
191 .setWhen(System.currentTimeMillis())
192 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900193 .setSmallIcon(icon)
194 .setAutoCancel(true)
195 .setTicker(title)
196 .setColor(mContext.getColor(
197 com.android.internal.R.color.system_notification_accent_color))
198 .setContentTitle(title)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900199 .setContentIntent(intent)
200 .setLocalOnly(true)
201 .setPriority(highPriority ?
202 Notification.PRIORITY_HIGH :
203 Notification.PRIORITY_DEFAULT)
204 .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900205 .setOnlyAlertOnce(true);
206
207 if (notifyType == NotificationType.NETWORK_SWITCH) {
208 builder.setStyle(new Notification.BigTextStyle().bigText(details));
209 } else {
210 builder.setContentText(details);
211 }
212
213 Notification notification = builder.build();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900214
Hugo Benichifb2609d2016-12-08 09:36:52 +0900215 mNotificationTypeMap.put(id, eventId);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900216 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900217 mNotificationManager.notifyAsUser(tag, eventId, notification, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900218 } catch (NullPointerException npe) {
Hugo Benichi8b025bf2016-11-21 13:50:05 +0900219 Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900220 }
221 }
222
223 public void clearNotification(int id) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900224 if (mNotificationTypeMap.indexOfKey(id) < 0) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900225 return;
226 }
Hugo Benichi1f0d9722016-12-22 09:49:11 +0900227 final String tag = tagFor(id);
Hugo Benichifb2609d2016-12-08 09:36:52 +0900228 final int eventId = mNotificationTypeMap.get(id);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900229 if (DBG) {
Paul Stewart835cb492016-12-19 08:45:32 -0800230 Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag,
231 nameOf(eventId)));
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900232 }
233 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900234 mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900235 } catch (NullPointerException npe) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900236 Slog.d(TAG, String.format(
Paul Stewart835cb492016-12-19 08:45:32 -0800237 "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900238 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900239 mNotificationTypeMap.delete(id);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900240 }
241
242 /**
243 * Legacy provisioning notifications coming directly from DcTracker.
244 */
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900245 public void setProvNotificationVisible(boolean visible, int id, String action) {
246 if (visible) {
247 Intent intent = new Intent(action);
248 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900249 showNotification(id, NotificationType.SIGN_IN, null, null, pendingIntent, false);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900250 } else {
251 clearNotification(id);
252 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900253 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900254
255 public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
256 String fromTransport = getTransportName(getFirstTransportType(fromNai));
257 String toTransport = getTransportName(getFirstTransportType(toNai));
258 String text = mContext.getResources().getString(
259 R.string.network_switch_metered_toast, fromTransport, toTransport);
260 Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
261 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900262
263 @VisibleForTesting
264 static String tagFor(int id) {
265 return String.format("ConnectivityNotification:%d", id);
266 }
267
268 @VisibleForTesting
269 static String nameOf(int eventId) {
270 NotificationType t = NotificationType.getFromId(eventId);
271 return (t != null) ? t.name() : "UNKNOWN";
272 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900273}