• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.connectivity;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.widget.Toast;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Resources;
26 import android.net.NetworkCapabilities;
27 import android.os.UserHandle;
28 import android.telephony.TelephonyManager;
29 import android.util.Slog;
30 
31 import com.android.internal.R;
32 
33 import static android.net.NetworkCapabilities.*;
34 
35 
36 public class NetworkNotificationManager {
37 
38     public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH };
39 
40     private static final String NOTIFICATION_ID = "Connectivity.Notification";
41 
42     private static final String TAG = NetworkNotificationManager.class.getSimpleName();
43     private static final boolean DBG = true;
44     private static final boolean VDBG = false;
45 
46     private final Context mContext;
47     private final TelephonyManager mTelephonyManager;
48     private final NotificationManager mNotificationManager;
49 
NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n)50     public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) {
51         mContext = c;
52         mTelephonyManager = t;
53         mNotificationManager = n;
54     }
55 
56     // TODO: deal more gracefully with multi-transport networks.
getFirstTransportType(NetworkAgentInfo nai)57     private static int getFirstTransportType(NetworkAgentInfo nai) {
58         for (int i = 0; i < 64; i++) {
59             if (nai.networkCapabilities.hasTransport(i)) return i;
60         }
61         return -1;
62     }
63 
getTransportName(int transportType)64     private static String getTransportName(int transportType) {
65         Resources r = Resources.getSystem();
66         String[] networkTypes = r.getStringArray(R.array.network_switch_type_name);
67         try {
68             return networkTypes[transportType];
69         } catch (IndexOutOfBoundsException e) {
70             return r.getString(R.string.network_switch_type_name_unknown);
71         }
72     }
73 
getIcon(int transportType)74     private static int getIcon(int transportType) {
75         return (transportType == TRANSPORT_WIFI) ?
76                 R.drawable.stat_notify_wifi_in_range :  // TODO: Distinguish ! from ?.
77                 R.drawable.stat_notify_rssi_in_range;
78     }
79 
80     /**
81      * Show or hide network provisioning notifications.
82      *
83      * We use notifications for two purposes: to notify that a network requires sign in
84      * (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
85      * (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
86      * particular network we can display the notification type that was most recently requested.
87      * So for example if a captive portal fails to reply within a few seconds of connecting, we
88      * might first display NO_INTERNET, and then when the captive portal check completes, display
89      * SIGN_IN.
90      *
91      * @param id an identifier that uniquely identifies this notification.  This must match
92      *         between show and hide calls.  We use the NetID value but for legacy callers
93      *         we concatenate the range of types with the range of NetIDs.
94      * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
95      *         or LOST_INTERNET notification, this is the network we're connecting to. For a
96      *         NETWORK_SWITCH notification it's the network that we switched from. When this network
97      *         disconnects the notification is removed.
98      * @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null
99      *         in all other cases. Only used to determine the text of the notification.
100      */
showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai, NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority)101     public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai,
102             NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) {
103         int transportType;
104         String extraInfo;
105         if (nai != null) {
106             transportType = getFirstTransportType(nai);
107             extraInfo = nai.networkInfo.getExtraInfo();
108             // Only notify for Internet-capable networks.
109             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
110         } else {
111             // Legacy notifications.
112             transportType = TRANSPORT_CELLULAR;
113             extraInfo = null;
114         }
115 
116         if (DBG) {
117             Slog.d(TAG, "showNotification " + notifyType
118                     + " transportType=" + getTransportName(transportType)
119                     + " extraInfo=" + extraInfo + " highPriority=" + highPriority);
120         }
121 
122         Resources r = Resources.getSystem();
123         CharSequence title;
124         CharSequence details;
125         int icon = getIcon(transportType);
126         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
127             title = r.getString(R.string.wifi_no_internet, 0);
128             details = r.getString(R.string.wifi_no_internet_detailed);
129         } else if (notifyType == NotificationType.LOST_INTERNET &&
130                 transportType == TRANSPORT_WIFI) {
131             title = r.getString(R.string.wifi_no_internet, 0);
132             details = r.getString(R.string.wifi_no_internet_detailed);
133         } else if (notifyType == NotificationType.SIGN_IN) {
134             switch (transportType) {
135                 case TRANSPORT_WIFI:
136                     title = r.getString(R.string.wifi_available_sign_in, 0);
137                     details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
138                     break;
139                 case TRANSPORT_CELLULAR:
140                     title = r.getString(R.string.network_available_sign_in, 0);
141                     // TODO: Change this to pull from NetworkInfo once a printable
142                     // name has been added to it
143                     details = mTelephonyManager.getNetworkOperatorName();
144                     break;
145                 default:
146                     title = r.getString(R.string.network_available_sign_in, 0);
147                     details = r.getString(R.string.network_available_sign_in_detailed, extraInfo);
148                     break;
149             }
150         } else if (notifyType == NotificationType.NETWORK_SWITCH) {
151             String fromTransport = getTransportName(transportType);
152             String toTransport = getTransportName(getFirstTransportType(switchToNai));
153             title = r.getString(R.string.network_switch_metered, toTransport);
154             details = r.getString(R.string.network_switch_metered_detail, toTransport,
155                     fromTransport);
156         } else {
157             Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network transport "
158                     + getTransportName(transportType));
159             return;
160         }
161 
162         Notification.Builder builder = new Notification.Builder(mContext)
163                 .setWhen(System.currentTimeMillis())
164                 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
165                 .setSmallIcon(icon)
166                 .setAutoCancel(true)
167                 .setTicker(title)
168                 .setColor(mContext.getColor(
169                         com.android.internal.R.color.system_notification_accent_color))
170                 .setContentTitle(title)
171                 .setContentIntent(intent)
172                 .setLocalOnly(true)
173                 .setPriority(highPriority ?
174                         Notification.PRIORITY_HIGH :
175                         Notification.PRIORITY_DEFAULT)
176                 .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
177                 .setOnlyAlertOnce(true);
178 
179         if (notifyType == NotificationType.NETWORK_SWITCH) {
180             builder.setStyle(new Notification.BigTextStyle().bigText(details));
181         } else {
182             builder.setContentText(details);
183         }
184 
185         Notification notification = builder.build();
186 
187         try {
188             mNotificationManager.notifyAsUser(NOTIFICATION_ID, id, notification, UserHandle.ALL);
189         } catch (NullPointerException npe) {
190             Slog.d(TAG, "setNotificationVisible: visible notificationManager npe=" + npe);
191         }
192     }
193 
clearNotification(int id)194     public void clearNotification(int id) {
195         if (DBG) {
196             Slog.d(TAG, "clearNotification id=" + id);
197         }
198         try {
199             mNotificationManager.cancelAsUser(NOTIFICATION_ID, id, UserHandle.ALL);
200         } catch (NullPointerException npe) {
201             Slog.d(TAG, "setNotificationVisible: cancel notificationManager npe=" + npe);
202         }
203     }
204 
205     /**
206      * Legacy provisioning notifications coming directly from DcTracker.
207      */
setProvNotificationVisible(boolean visible, int id, String action)208     public void setProvNotificationVisible(boolean visible, int id, String action) {
209         if (visible) {
210             Intent intent = new Intent(action);
211             PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
212             showNotification(id, NotificationType.SIGN_IN, null, null, pendingIntent, false);
213         } else {
214             clearNotification(id);
215         }
216     }
217 
showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai)218     public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
219         String fromTransport = getTransportName(getFirstTransportType(fromNai));
220         String toTransport = getTransportName(getFirstTransportType(toNai));
221         String text = mContext.getResources().getString(
222                 R.string.network_switch_metered_toast, fromTransport, toTransport);
223         Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
224     }
225 }
226