blob: 0d935dba22c601e355f248dd546adba7b18886b0 [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;
Chris Wrenf8c4f622017-03-31 13:52:39 -040034import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Geoffrey Pitschaf759c52017-02-15 09:35:38 -050035import com.android.internal.notification.SystemNotificationChannels;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090036
Hugo Benichifb2609d2016-12-08 09:36:52 +090037import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
38import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
39import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090040
41public class NetworkNotificationManager {
42
Chris Wrenf8c4f622017-03-31 13:52:39 -040043
Hugo Benichifb2609d2016-12-08 09:36:52 +090044 public static enum NotificationType {
Chris Wrenf8c4f622017-03-31 13:52:39 -040045 LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
46 NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
47 NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
48 SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090049
Hugo Benichifb2609d2016-12-08 09:36:52 +090050 public final int eventId;
51
52 NotificationType(int eventId) {
53 this.eventId = eventId;
54 Holder.sIdToTypeMap.put(eventId, this);
55 }
56
57 private static class Holder {
58 private static SparseArray<NotificationType> sIdToTypeMap = new SparseArray<>();
59 }
60
61 public static NotificationType getFromId(int id) {
62 return Holder.sIdToTypeMap.get(id);
63 }
64 };
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090065
66 private static final String TAG = NetworkNotificationManager.class.getSimpleName();
67 private static final boolean DBG = true;
68 private static final boolean VDBG = false;
69
70 private final Context mContext;
71 private final TelephonyManager mTelephonyManager;
Lorenzo Colitti0b599062016-08-22 22:36:19 +090072 private final NotificationManager mNotificationManager;
Hugo Benichifb2609d2016-12-08 09:36:52 +090073 // Tracks the types of notifications managed by this instance, from creation to cancellation.
74 private final SparseIntArray mNotificationTypeMap;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +090075
Lorenzo Colitti0b599062016-08-22 22:36:19 +090076 public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) {
77 mContext = c;
78 mTelephonyManager = t;
79 mNotificationManager = n;
Hugo Benichifb2609d2016-12-08 09:36:52 +090080 mNotificationTypeMap = new SparseIntArray();
Lorenzo Colitti0b599062016-08-22 22:36:19 +090081 }
82
83 // TODO: deal more gracefully with multi-transport networks.
84 private static int getFirstTransportType(NetworkAgentInfo nai) {
85 for (int i = 0; i < 64; i++) {
86 if (nai.networkCapabilities.hasTransport(i)) return i;
87 }
88 return -1;
89 }
90
91 private static String getTransportName(int transportType) {
92 Resources r = Resources.getSystem();
93 String[] networkTypes = r.getStringArray(R.array.network_switch_type_name);
94 try {
95 return networkTypes[transportType];
96 } catch (IndexOutOfBoundsException e) {
97 return r.getString(R.string.network_switch_type_name_unknown);
98 }
99 }
100
101 private static int getIcon(int transportType) {
102 return (transportType == TRANSPORT_WIFI) ?
103 R.drawable.stat_notify_wifi_in_range : // TODO: Distinguish ! from ?.
104 R.drawable.stat_notify_rssi_in_range;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900105 }
106
107 /**
108 * Show or hide network provisioning notifications.
109 *
110 * We use notifications for two purposes: to notify that a network requires sign in
111 * (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
112 * (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
113 * particular network we can display the notification type that was most recently requested.
114 * So for example if a captive portal fails to reply within a few seconds of connecting, we
115 * might first display NO_INTERNET, and then when the captive portal check completes, display
116 * SIGN_IN.
117 *
118 * @param id an identifier that uniquely identifies this notification. This must match
119 * between show and hide calls. We use the NetID value but for legacy callers
120 * we concatenate the range of types with the range of NetIDs.
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900121 * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
122 * or LOST_INTERNET notification, this is the network we're connecting to. For a
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900123 * NETWORK_SWITCH notification it's the network that we switched from. When this network
124 * disconnects the notification is removed.
125 * @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null
126 * in all other cases. Only used to determine the text of the notification.
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900127 */
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900128 public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai,
129 NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900130 final String tag = tagFor(id);
131 final int eventId = notifyType.eventId;
132 final int transportType;
133 final String extraInfo;
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900134 if (nai != null) {
135 transportType = getFirstTransportType(nai);
136 extraInfo = nai.networkInfo.getExtraInfo();
137 // Only notify for Internet-capable networks.
138 if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
139 } else {
140 // Legacy notifications.
141 transportType = TRANSPORT_CELLULAR;
142 extraInfo = null;
143 }
144
Hugo Benichi5fcd0502017-07-25 21:57:51 +0900145 // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
146 // A new SIGN_IN notification with a new intent should override any existing one.
147 final int previousEventId = mNotificationTypeMap.get(id);
148 final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
149 if (priority(previousNotifyType) > priority(notifyType)) {
150 Slog.d(TAG, String.format(
151 "ignoring notification %s for network %s with existing notification %s",
152 notifyType, id, previousNotifyType));
153 return;
154 }
155 clearNotification(id);
156
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900157 if (DBG) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900158 Slog.d(TAG, String.format(
Joe LaPennae1406162016-12-19 10:31:14 -0800159 "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
Paul Stewart835cb492016-12-19 08:45:32 -0800160 tag, nameOf(eventId), getTransportName(transportType), extraInfo,
161 highPriority));
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900162 }
163
164 Resources r = Resources.getSystem();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900165 CharSequence title;
166 CharSequence details;
167 int icon = getIcon(transportType);
168 if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
169 title = r.getString(R.string.wifi_no_internet, 0);
170 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti9be58c52016-09-15 14:02:29 +0900171 } else if (notifyType == NotificationType.LOST_INTERNET &&
172 transportType == TRANSPORT_WIFI) {
173 title = r.getString(R.string.wifi_no_internet, 0);
174 details = r.getString(R.string.wifi_no_internet_detailed);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900175 } else if (notifyType == NotificationType.SIGN_IN) {
176 switch (transportType) {
177 case TRANSPORT_WIFI:
178 title = r.getString(R.string.wifi_available_sign_in, 0);
179 details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
180 break;
181 case TRANSPORT_CELLULAR:
182 title = r.getString(R.string.network_available_sign_in, 0);
183 // TODO: Change this to pull from NetworkInfo once a printable
184 // name has been added to it
185 details = mTelephonyManager.getNetworkOperatorName();
186 break;
187 default:
188 title = r.getString(R.string.network_available_sign_in, 0);
189 details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
190 break;
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900191 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900192 } else if (notifyType == NotificationType.NETWORK_SWITCH) {
193 String fromTransport = getTransportName(transportType);
194 String toTransport = getTransportName(getFirstTransportType(switchToNai));
195 title = r.getString(R.string.network_switch_metered, toTransport);
196 details = r.getString(R.string.network_switch_metered_detail, toTransport,
197 fromTransport);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900198 } else {
Hugo Benichia5bf8192016-12-20 09:57:43 +0900199 Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900200 + getTransportName(transportType));
201 return;
202 }
203
Geoffrey Pitschaf759c52017-02-15 09:35:38 -0500204 final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS :
205 SystemNotificationChannels.NETWORK_STATUS;
206 Notification.Builder builder = new Notification.Builder(mContext, channelId)
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900207 .setWhen(System.currentTimeMillis())
208 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900209 .setSmallIcon(icon)
210 .setAutoCancel(true)
211 .setTicker(title)
212 .setColor(mContext.getColor(
213 com.android.internal.R.color.system_notification_accent_color))
214 .setContentTitle(title)
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900215 .setContentIntent(intent)
216 .setLocalOnly(true)
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900217 .setOnlyAlertOnce(true);
218
219 if (notifyType == NotificationType.NETWORK_SWITCH) {
220 builder.setStyle(new Notification.BigTextStyle().bigText(details));
221 } else {
222 builder.setContentText(details);
223 }
224
Rhiannon Malia6ab65522017-06-05 15:02:55 -0700225 if (notifyType == NotificationType.SIGN_IN) {
226 builder.extend(new Notification.TvExtender().setChannelId(channelId));
227 }
228
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900229 Notification notification = builder.build();
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900230
Hugo Benichifb2609d2016-12-08 09:36:52 +0900231 mNotificationTypeMap.put(id, eventId);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900232 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900233 mNotificationManager.notifyAsUser(tag, eventId, notification, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900234 } catch (NullPointerException npe) {
Hugo Benichi8b025bf2016-11-21 13:50:05 +0900235 Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900236 }
237 }
238
239 public void clearNotification(int id) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900240 if (mNotificationTypeMap.indexOfKey(id) < 0) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900241 return;
242 }
Hugo Benichi1f0d9722016-12-22 09:49:11 +0900243 final String tag = tagFor(id);
Hugo Benichifb2609d2016-12-08 09:36:52 +0900244 final int eventId = mNotificationTypeMap.get(id);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900245 if (DBG) {
Paul Stewart835cb492016-12-19 08:45:32 -0800246 Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag,
247 nameOf(eventId)));
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900248 }
249 try {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900250 mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900251 } catch (NullPointerException npe) {
Hugo Benichifb2609d2016-12-08 09:36:52 +0900252 Slog.d(TAG, String.format(
Paul Stewart835cb492016-12-19 08:45:32 -0800253 "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900254 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900255 mNotificationTypeMap.delete(id);
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900256 }
257
258 /**
259 * Legacy provisioning notifications coming directly from DcTracker.
260 */
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900261 public void setProvNotificationVisible(boolean visible, int id, String action) {
262 if (visible) {
263 Intent intent = new Intent(action);
264 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900265 showNotification(id, NotificationType.SIGN_IN, null, null, pendingIntent, false);
Lorenzo Colitti0b599062016-08-22 22:36:19 +0900266 } else {
267 clearNotification(id);
268 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900269 }
Lorenzo Colitti5526f9c2016-08-22 16:46:40 +0900270
271 public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
272 String fromTransport = getTransportName(getFirstTransportType(fromNai));
273 String toTransport = getTransportName(getFirstTransportType(toNai));
274 String text = mContext.getResources().getString(
275 R.string.network_switch_metered_toast, fromTransport, toTransport);
276 Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
277 }
Hugo Benichifb2609d2016-12-08 09:36:52 +0900278
279 @VisibleForTesting
280 static String tagFor(int id) {
281 return String.format("ConnectivityNotification:%d", id);
282 }
283
284 @VisibleForTesting
285 static String nameOf(int eventId) {
286 NotificationType t = NotificationType.getFromId(eventId);
287 return (t != null) ? t.name() : "UNKNOWN";
288 }
Hugo Benichi5fcd0502017-07-25 21:57:51 +0900289
290 private static int priority(NotificationType t) {
291 if (t == null) {
292 return 0;
293 }
294 switch (t) {
295 case SIGN_IN:
296 return 4;
297 case NO_INTERNET:
298 return 3;
299 case NETWORK_SWITCH:
300 return 2;
301 case LOST_INTERNET:
302 return 1;
303 default:
304 return 0;
305 }
306 }
Lorenzo Colittif3ae2ee2016-08-22 16:30:00 +0900307}