blob: 02459bde09a8e2fc3e741f47079db6de6926a40b [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;
Chalard Jean2dcccbc2018-04-12 11:52:37 +090026import android.net.wifi.WifiInfo;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090027import android.os.UserHandle;
28import android.telephony.TelephonyManager;
29import android.util.Slog;
Hugo Benichifb2609d2016-12-08 09:36:52 +090030import android.util.SparseArray;
31import android.util.SparseIntArray;
32import android.widget.Toast;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090033import com.android.internal.R;
Hugo Benichifb2609d2016-12-08 09:36:52 +090034import com.android.internal.annotations.VisibleForTesting;
Chris Wrenf8c4f622017-03-31 13:52:39 -040035import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Geoffrey Pitschaf759c52017-02-15 09:35:38 -050036import com.android.internal.notification.SystemNotificationChannels;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090037
Hugo Benichifb2609d2016-12-08 09:36:52 +090038import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
39import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
40import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090041
42public class NetworkNotificationManager {
43
Chris Wrenf8c4f622017-03-31 13:52:39 -040044
Hugo Benichifb2609d2016-12-08 09:36:52 +090045 public static enum NotificationType {
Chris Wrenf8c4f622017-03-31 13:52:39 -040046 LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
47 NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
48 NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
49 SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090050
Hugo Benichifb2609d2016-12-08 09:36:52 +090051 public final int eventId;
52
53 NotificationType(int eventId) {
54 this.eventId = eventId;
55 Holder.sIdToTypeMap.put(eventId, this);
56 }
57
58 private static class Holder {
59 private static SparseArray<NotificationType> sIdToTypeMap = new SparseArray<>();
60 }
61
62 public static NotificationType getFromId(int id) {
63 return Holder.sIdToTypeMap.get(id);
64 }
65 };
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090066
67 private static final String TAG = NetworkNotificationManager.class.getSimpleName();
68 private static final boolean DBG = true;
69 private static final boolean VDBG = false;
70
71 private final Context mContext;
72 private final TelephonyManager mTelephonyManager;
Lorenzo Colitti0b599062016-08-22 22:36:19 +090073 private final NotificationManager mNotificationManager;
Hugo Benichifb2609d2016-12-08 09:36:52 +090074 // Tracks the types of notifications managed by this instance, from creation to cancellation.
75 private final SparseIntArray mNotificationTypeMap;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090076
Lorenzo Colitti0b599062016-08-22 22:36:19 +090077 public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) {
78 mContext = c;
79 mTelephonyManager = t;
80 mNotificationManager = n;
Hugo Benichifb2609d2016-12-08 09:36:52 +090081 mNotificationTypeMap = new SparseIntArray();
Lorenzo Colitti0b599062016-08-22 22:36:19 +090082 }
83
84 // TODO: deal more gracefully with multi-transport networks.
85 private static int getFirstTransportType(NetworkAgentInfo nai) {
86 for (int i = 0; i < 64; i++) {
87 if (nai.networkCapabilities.hasTransport(i)) return i;
88 }
89 return -1;
90 }
91
92 private static String getTransportName(int transportType) {
93 Resources r = Resources.getSystem();
94 String[] networkTypes = r.getStringArray(R.array.network_switch_type_name);
95 try {
96 return networkTypes[transportType];
97 } catch (IndexOutOfBoundsException e) {
98 return r.getString(R.string.network_switch_type_name_unknown);
99 }
100 }
101
102 private static int getIcon(int transportType) {
103 return (transportType == TRANSPORT_WIFI) ?
104 R.drawable.stat_notify_wifi_in_range : // TODO: Distinguish ! from ?.
105 R.drawable.stat_notify_rssi_in_range;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900106 }
107
108 /**
109 * Show or hide network provisioning notifications.
110 *
111 * We use notifications for two purposes: to notify that a network requires sign in
112 * (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
113 * (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
114 * particular network we can display the notification type that was most recently requested.
115 * So for example if a captive portal fails to reply within a few seconds of connecting, we
116 * might first display NO_INTERNET, and then when the captive portal check completes, display
117 * SIGN_IN.
118 *
119 * @param id an identifier that uniquely identifies this notification. This must match
120 * between show and hide calls. We use the NetID value but for legacy callers
121 * we concatenate the range of types with the range of NetIDs.
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900122 * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
123 * or LOST_INTERNET notification, this is the network we're connecting to. For a
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900124 * NETWORK_SWITCH notification it's the network that we switched from. When this network
125 * disconnects the notification is removed.
126 * @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null
127 * in all other cases. Only used to determine the text of the notification.
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900128 */
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900129 public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai,
130 NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900131 final String tag = tagFor(id);
132 final int eventId = notifyType.eventId;
133 final int transportType;
134 final String extraInfo;
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900135 if (nai != null) {
136 transportType = getFirstTransportType(nai);
137 extraInfo = nai.networkInfo.getExtraInfo();
138 // Only notify for Internet-capable networks.
139 if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
140 } else {
141 // Legacy notifications.
142 transportType = TRANSPORT_CELLULAR;
143 extraInfo = null;
144 }
145
Hugo Benichi5fcd0502017-07-25 21:57:51 +0900146 // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
147 // A new SIGN_IN notification with a new intent should override any existing one.
148 final int previousEventId = mNotificationTypeMap.get(id);
149 final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
150 if (priority(previousNotifyType) > priority(notifyType)) {
151 Slog.d(TAG, String.format(
152 "ignoring notification %s for network %s with existing notification %s",
153 notifyType, id, previousNotifyType));
154 return;
155 }
156 clearNotification(id);
157
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900158 if (DBG) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900159 Slog.d(TAG, String.format(
Joe LaPennae1406162016-12-19 10:31:14 -0800160 "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
Paul Stewart835cb492016-12-19 08:45:32 -0800161 tag, nameOf(eventId), getTransportName(transportType), extraInfo,
162 highPriority));
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900163 }
164
165 Resources r = Resources.getSystem();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900166 CharSequence title;
167 CharSequence details;
168 int icon = getIcon(transportType);
169 if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
170 title = r.getString(R.string.wifi_no_internet, 0);
171 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900172 } else if (notifyType == NotificationType.LOST_INTERNET &&
173 transportType == TRANSPORT_WIFI) {
174 title = r.getString(R.string.wifi_no_internet, 0);
175 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900176 } else if (notifyType == NotificationType.SIGN_IN) {
177 switch (transportType) {
178 case TRANSPORT_WIFI:
179 title = r.getString(R.string.wifi_available_sign_in, 0);
Chalard Jean2dcccbc2018-04-12 11:52:37 +0900180 details = r.getString(R.string.network_available_sign_in_detailed,
181 WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900182 break;
183 case TRANSPORT_CELLULAR:
184 title = r.getString(R.string.network_available_sign_in, 0);
185 // TODO: Change this to pull from NetworkInfo once a printable
186 // name has been added to it
187 details = mTelephonyManager.getNetworkOperatorName();
188 break;
189 default:
190 title = r.getString(R.string.network_available_sign_in, 0);
191 details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
192 break;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900193 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900194 } else if (notifyType == NotificationType.NETWORK_SWITCH) {
195 String fromTransport = getTransportName(transportType);
196 String toTransport = getTransportName(getFirstTransportType(switchToNai));
197 title = r.getString(R.string.network_switch_metered, toTransport);
198 details = r.getString(R.string.network_switch_metered_detail, toTransport,
199 fromTransport);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900200 } else {
Hugo Benichia5bf8192016-12-20 09:57:43 +0900201 Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900202 + getTransportName(transportType));
203 return;
204 }
205
Geoffrey Pitschaf759c52017-02-15 09:35:38 -0500206 final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS :
207 SystemNotificationChannels.NETWORK_STATUS;
208 Notification.Builder builder = new Notification.Builder(mContext, channelId)
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900209 .setWhen(System.currentTimeMillis())
210 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900211 .setSmallIcon(icon)
212 .setAutoCancel(true)
213 .setTicker(title)
214 .setColor(mContext.getColor(
215 com.android.internal.R.color.system_notification_accent_color))
216 .setContentTitle(title)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900217 .setContentIntent(intent)
218 .setLocalOnly(true)
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900219 .setOnlyAlertOnce(true);
220
221 if (notifyType == NotificationType.NETWORK_SWITCH) {
222 builder.setStyle(new Notification.BigTextStyle().bigText(details));
223 } else {
224 builder.setContentText(details);
225 }
226
Rhiannon Malia6ab65522017-06-05 15:02:55 -0700227 if (notifyType == NotificationType.SIGN_IN) {
228 builder.extend(new Notification.TvExtender().setChannelId(channelId));
229 }
230
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900231 Notification notification = builder.build();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900232
Hugo Benichifb2609d2016-12-08 09:36:52 +0900233 mNotificationTypeMap.put(id, eventId);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900234 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900235 mNotificationManager.notifyAsUser(tag, eventId, notification, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900236 } catch (NullPointerException npe) {
Hugo Benichi8b025bf2016-11-21 13:50:05 +0900237 Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900238 }
239 }
240
241 public void clearNotification(int id) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900242 if (mNotificationTypeMap.indexOfKey(id) < 0) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900243 return;
244 }
Hugo Benichi1f0d9722016-12-22 09:49:11 +0900245 final String tag = tagFor(id);
Hugo Benichifb2609d2016-12-08 09:36:52 +0900246 final int eventId = mNotificationTypeMap.get(id);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900247 if (DBG) {
Paul Stewart835cb492016-12-19 08:45:32 -0800248 Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag,
249 nameOf(eventId)));
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900250 }
251 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900252 mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900253 } catch (NullPointerException npe) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900254 Slog.d(TAG, String.format(
Paul Stewart835cb492016-12-19 08:45:32 -0800255 "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900256 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900257 mNotificationTypeMap.delete(id);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900258 }
259
260 /**
261 * Legacy provisioning notifications coming directly from DcTracker.
262 */
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900263 public void setProvNotificationVisible(boolean visible, int id, String action) {
264 if (visible) {
265 Intent intent = new Intent(action);
266 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900267 showNotification(id, NotificationType.SIGN_IN, null, null, pendingIntent, false);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900268 } else {
269 clearNotification(id);
270 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900271 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900272
273 public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
274 String fromTransport = getTransportName(getFirstTransportType(fromNai));
275 String toTransport = getTransportName(getFirstTransportType(toNai));
276 String text = mContext.getResources().getString(
277 R.string.network_switch_metered_toast, fromTransport, toTransport);
278 Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
279 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900280
281 @VisibleForTesting
282 static String tagFor(int id) {
283 return String.format("ConnectivityNotification:%d", id);
284 }
285
286 @VisibleForTesting
287 static String nameOf(int eventId) {
288 NotificationType t = NotificationType.getFromId(eventId);
289 return (t != null) ? t.name() : "UNKNOWN";
290 }
Hugo Benichi5fcd0502017-07-25 21:57:51 +0900291
292 private static int priority(NotificationType t) {
293 if (t == null) {
294 return 0;
295 }
296 switch (t) {
297 case SIGN_IN:
298 return 4;
299 case NO_INTERNET:
300 return 3;
301 case NETWORK_SWITCH:
302 return 2;
303 case LOST_INTERNET:
304 return 1;
305 default:
306 return 0;
307 }
308 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900309}