Home | History | Annotate | Download | only in connectivity
      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 
     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