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 17package com.android.server.connectivity; 18 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.widget.Toast; 23import android.content.Context; 24import android.content.Intent; 25import android.content.res.Resources; 26import android.net.NetworkCapabilities; 27import android.os.UserHandle; 28import android.telephony.TelephonyManager; 29import android.util.Slog; 30 31import com.android.internal.R; 32 33import static android.net.NetworkCapabilities.*; 34 35 36public 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 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. 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 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 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 */ 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 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 */ 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 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