Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2006 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.phone;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.app.StatusBarManager;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.SharedPreferences;
     27 import android.content.pm.UserInfo;
     28 import android.content.res.Resources;
     29 import android.net.Uri;
     30 import android.os.PersistableBundle;
     31 import android.os.SystemProperties;
     32 import android.os.UserHandle;
     33 import android.os.UserManager;
     34 import android.preference.PreferenceManager;
     35 import android.provider.ContactsContract.PhoneLookup;
     36 import android.telecom.PhoneAccount;
     37 import android.telecom.PhoneAccountHandle;
     38 import android.telecom.TelecomManager;
     39 import android.telephony.CarrierConfigManager;
     40 import android.telephony.PhoneNumberUtils;
     41 import android.telephony.ServiceState;
     42 import android.telephony.SubscriptionInfo;
     43 import android.telephony.SubscriptionManager;
     44 import android.telephony.TelephonyManager;
     45 import android.text.TextUtils;
     46 import android.util.ArrayMap;
     47 import android.util.Log;
     48 import android.widget.Toast;
     49 
     50 import com.android.internal.telephony.Phone;
     51 import com.android.internal.telephony.TelephonyCapabilities;
     52 import com.android.phone.settings.VoicemailSettingsActivity;
     53 import com.android.phone.vvm.omtp.sync.VoicemailStatusQueryHelper;
     54 import com.android.phone.settings.VoicemailNotificationSettingsUtil;
     55 
     56 import java.util.Iterator;
     57 import java.util.List;
     58 import java.util.Set;
     59 
     60 /**
     61  * NotificationManager-related utility code for the Phone app.
     62  *
     63  * This is a singleton object which acts as the interface to the
     64  * framework's NotificationManager, and is used to display status bar
     65  * icons and control other status bar-related behavior.
     66  *
     67  * @see PhoneGlobals.notificationMgr
     68  */
     69 public class NotificationMgr {
     70     private static final String LOG_TAG = NotificationMgr.class.getSimpleName();
     71     private static final boolean DBG =
     72             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     73     // Do not check in with VDBG = true, since that may write PII to the system log.
     74     private static final boolean VDBG = false;
     75 
     76     // notification types
     77     static final int MMI_NOTIFICATION = 1;
     78     static final int NETWORK_SELECTION_NOTIFICATION = 2;
     79     static final int VOICEMAIL_NOTIFICATION = 3;
     80     static final int CALL_FORWARD_NOTIFICATION = 4;
     81     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 5;
     82     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
     83 
     84     /** The singleton NotificationMgr instance. */
     85     private static NotificationMgr sInstance;
     86 
     87     private PhoneGlobals mApp;
     88     private Phone mPhone;
     89 
     90     private Context mContext;
     91     private NotificationManager mNotificationManager;
     92     private final ComponentName mNotificationComponent;
     93     private StatusBarManager mStatusBarManager;
     94     private UserManager mUserManager;
     95     private Toast mToast;
     96     private SubscriptionManager mSubscriptionManager;
     97     private TelecomManager mTelecomManager;
     98     private TelephonyManager mTelephonyManager;
     99 
    100     // used to track the notification of selected network unavailable
    101     private boolean mSelectedUnavailableNotify = false;
    102 
    103     // used to track whether the message waiting indicator is visible, per subscription id.
    104     private ArrayMap<Integer, Boolean> mMwiVisible = new ArrayMap<Integer, Boolean>();
    105 
    106     /**
    107      * Private constructor (this is a singleton).
    108      * @see #init(PhoneGlobals)
    109      */
    110     private NotificationMgr(PhoneGlobals app) {
    111         mApp = app;
    112         mContext = app;
    113         mNotificationManager =
    114                 (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
    115         mStatusBarManager =
    116                 (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
    117         mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
    118         mPhone = app.mCM.getDefaultPhone();
    119         mSubscriptionManager = SubscriptionManager.from(mContext);
    120         mTelecomManager = TelecomManager.from(mContext);
    121         mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
    122 
    123         final String notificationComponent = mContext.getString(
    124                 R.string.config_customVoicemailComponent);
    125 
    126         mNotificationComponent = notificationComponent != null
    127                 ? ComponentName.unflattenFromString(notificationComponent) : null;
    128     }
    129 
    130     /**
    131      * Initialize the singleton NotificationMgr instance.
    132      *
    133      * This is only done once, at startup, from PhoneApp.onCreate().
    134      * From then on, the NotificationMgr instance is available via the
    135      * PhoneApp's public "notificationMgr" field, which is why there's no
    136      * getInstance() method here.
    137      */
    138     /* package */ static NotificationMgr init(PhoneGlobals app) {
    139         synchronized (NotificationMgr.class) {
    140             if (sInstance == null) {
    141                 sInstance = new NotificationMgr(app);
    142             } else {
    143                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
    144             }
    145             return sInstance;
    146         }
    147     }
    148 
    149     /** The projection to use when querying the phones table */
    150     static final String[] PHONES_PROJECTION = new String[] {
    151         PhoneLookup.NUMBER,
    152         PhoneLookup.DISPLAY_NAME,
    153         PhoneLookup._ID
    154     };
    155 
    156     /**
    157      * Re-creates the message waiting indicator (voicemail) notification if it is showing.  Used to
    158      * refresh the voicemail intent on the indicator when the user changes it via the voicemail
    159      * settings screen.  The voicemail notification sound is suppressed.
    160      *
    161      * @param subId The subscription Id.
    162      */
    163     /* package */ void refreshMwi(int subId) {
    164         // In a single-sim device, subId can be -1 which means "no sub id".  In this case we will
    165         // reference the single subid stored in the mMwiVisible map.
    166         if (subId == SubscriptionInfoHelper.NO_SUB_ID) {
    167             if (mMwiVisible.keySet().size() == 1) {
    168                 Set<Integer> keySet = mMwiVisible.keySet();
    169                 Iterator<Integer> keyIt = keySet.iterator();
    170                 if (!keyIt.hasNext()) {
    171                     return;
    172                 }
    173                 subId = keyIt.next();
    174             }
    175         }
    176         if (mMwiVisible.containsKey(subId)) {
    177             boolean mwiVisible = mMwiVisible.get(subId);
    178             if (mwiVisible) {
    179                 updateMwi(subId, mwiVisible, false /* enableNotificationSound */);
    180             }
    181         }
    182     }
    183 
    184     /**
    185      * Updates the message waiting indicator (voicemail) notification.
    186      *
    187      * @param visible true if there are messages waiting
    188      */
    189     /* package */ void updateMwi(int subId, boolean visible) {
    190         updateMwi(subId, visible, true /* enableNotificationSound */);
    191     }
    192 
    193     /**
    194      * Updates the message waiting indicator (voicemail) notification.
    195      *
    196      * @param subId the subId to update.
    197      * @param visible true if there are messages waiting
    198      * @param enableNotificationSound {@code true} if the notification sound should be played.
    199      */
    200     void updateMwi(int subId, boolean visible, boolean enableNotificationSound) {
    201         if (!PhoneGlobals.sVoiceCapable) {
    202             // Do not show the message waiting indicator on devices which are not voice capable.
    203             // These events *should* be blocked at the telephony layer for such devices.
    204             Log.w(LOG_TAG, "Called updateMwi() on non-voice-capable device! Ignoring...");
    205             return;
    206         }
    207 
    208         Phone phone = PhoneGlobals.getPhone(subId);
    209         if (visible && phone != null) {
    210             VoicemailStatusQueryHelper queryHelper = new VoicemailStatusQueryHelper(mContext);
    211             PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(phone);
    212             if (queryHelper.isNotificationsChannelActive(phoneAccount)) {
    213                 Log.v(LOG_TAG, "Notifications channel active for visual voicemail, hiding mwi.");
    214                 visible = false;
    215             }
    216         }
    217 
    218         Log.i(LOG_TAG, "updateMwi(): subId " + subId + " update to " + visible);
    219         mMwiVisible.put(subId, visible);
    220 
    221         if (visible) {
    222             if (phone == null) {
    223                 Log.w(LOG_TAG, "Found null phone for: " + subId);
    224                 return;
    225             }
    226 
    227             SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
    228             if (subInfo == null) {
    229                 Log.w(LOG_TAG, "Found null subscription info for: " + subId);
    230                 return;
    231             }
    232 
    233             int resId = android.R.drawable.stat_notify_voicemail;
    234 
    235             // This Notification can get a lot fancier once we have more
    236             // information about the current voicemail messages.
    237             // (For example, the current voicemail system can't tell
    238             // us the caller-id or timestamp of a message, or tell us the
    239             // message count.)
    240 
    241             // But for now, the UI is ultra-simple: if the MWI indication
    242             // is supposed to be visible, just show a single generic
    243             // notification.
    244 
    245             String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
    246             String vmNumber = phone.getVoiceMailNumber();
    247             if (DBG) log("- got vm number: '" + vmNumber + "'");
    248 
    249             // The voicemail number may be null because:
    250             //   (1) This phone has no voicemail number.
    251             //   (2) This phone has a voicemail number, but the SIM isn't ready yet. This may
    252             //       happen when the device first boots if we get a MWI notification when we
    253             //       register on the network before the SIM has loaded. In this case, the
    254             //       SubscriptionListener in CallNotifier will update this once the SIM is loaded.
    255             if ((vmNumber == null) && !phone.getIccRecordsLoaded()) {
    256                 if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
    257                 return;
    258             }
    259 
    260             Integer vmCount = null;
    261 
    262             if (TelephonyCapabilities.supportsVoiceMessageCount(phone)) {
    263                 vmCount = phone.getVoiceMessageCount();
    264                 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
    265                 notificationTitle = String.format(titleFormat, vmCount);
    266             }
    267 
    268             // This pathway only applies to PSTN accounts; only SIMS have subscription ids.
    269             PhoneAccountHandle phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
    270 
    271             Intent intent;
    272             String notificationText;
    273             boolean isSettingsIntent = TextUtils.isEmpty(vmNumber);
    274 
    275             if (isSettingsIntent) {
    276                 notificationText = mContext.getString(
    277                         R.string.notification_voicemail_no_vm_number);
    278 
    279                 // If the voicemail number if unknown, instead of calling voicemail, take the user
    280                 // to the voicemail settings.
    281                 notificationText = mContext.getString(
    282                         R.string.notification_voicemail_no_vm_number);
    283                 intent = new Intent(VoicemailSettingsActivity.ACTION_ADD_VOICEMAIL);
    284                 intent.putExtra(SubscriptionInfoHelper.SUB_ID_EXTRA, subId);
    285                 intent.setClass(mContext, VoicemailSettingsActivity.class);
    286             } else {
    287                 if (mTelephonyManager.getPhoneCount() > 1) {
    288                     notificationText = subInfo.getDisplayName().toString();
    289                 } else {
    290                     notificationText = String.format(
    291                             mContext.getString(R.string.notification_voicemail_text_format),
    292                             PhoneNumberUtils.formatNumber(vmNumber));
    293                 }
    294                 intent = new Intent(
    295                         Intent.ACTION_CALL, Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "",
    296                                 null));
    297                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
    298             }
    299 
    300             PendingIntent pendingIntent =
    301                     PendingIntent.getActivity(mContext, subId /* requestCode */, intent, 0);
    302             Uri ringtoneUri = null;
    303 
    304             if (enableNotificationSound) {
    305                 ringtoneUri = VoicemailNotificationSettingsUtil.getRingtoneUri(mPhone);
    306             }
    307 
    308             Resources res = mContext.getResources();
    309             PersistableBundle carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
    310                     mPhone.getSubId());
    311             Notification.Builder builder = new Notification.Builder(mContext);
    312             builder.setSmallIcon(resId)
    313                     .setWhen(System.currentTimeMillis())
    314                     .setColor(subInfo.getIconTint())
    315                     .setContentTitle(notificationTitle)
    316                     .setContentText(notificationText)
    317                     .setContentIntent(pendingIntent)
    318                     .setSound(ringtoneUri)
    319                     .setColor(res.getColor(R.color.dialer_theme_color))
    320                     .setOngoing(carrierConfig.getBoolean(
    321                             CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL));
    322 
    323             if (VoicemailNotificationSettingsUtil.isVibrationEnabled(phone)) {
    324                 builder.setDefaults(Notification.DEFAULT_VIBRATE);
    325             }
    326 
    327             final Notification notification = builder.build();
    328             List<UserInfo> users = mUserManager.getUsers(true);
    329             for (int i = 0; i < users.size(); i++) {
    330                 final UserInfo user = users.get(i);
    331                 final UserHandle userHandle = user.getUserHandle();
    332                 if (!mUserManager.hasUserRestriction(
    333                         UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
    334                         && !user.isManagedProfile()) {
    335                     if (!sendNotificationCustomComponent(vmCount, vmNumber, pendingIntent,
    336                             isSettingsIntent)) {
    337                         mNotificationManager.notifyAsUser(
    338                                 Integer.toString(subId) /* tag */,
    339                                 VOICEMAIL_NOTIFICATION,
    340                                 notification,
    341                                 userHandle);
    342                     }
    343                 }
    344             }
    345         } else {
    346             if (!sendNotificationCustomComponent(0, null, null, false)) {
    347                 mNotificationManager.cancelAsUser(
    348                         Integer.toString(subId) /* tag */,
    349                         VOICEMAIL_NOTIFICATION,
    350                         UserHandle.ALL);
    351             }
    352         }
    353     }
    354 
    355     /**
    356      * Sends a broadcast with the voicemail notification information to a custom component to
    357      * handle. This method is also used to indicate to the custom component when to clear the
    358      * notification. A pending intent can be passed to the custom component to indicate an action to
    359      * be taken as it would by a notification produced in this class.
    360      * @param count The number of pending voicemail messages to indicate on the notification. A
    361      *              Value of 0 is passed here to indicate that the notification should be cleared.
    362      * @param number The voicemail phone number if specified.
    363      * @param pendingIntent The intent that should be passed as the action to be taken.
    364      * @param isSettingsIntent {@code true} to indicate the pending intent is to launch settings.
    365      *                         otherwise, {@code false} to indicate the intent launches voicemail.
    366      * @return {@code true} if a custom component was notified of the notification.
    367      */
    368     private boolean sendNotificationCustomComponent(Integer count, String number,
    369             PendingIntent pendingIntent, boolean isSettingsIntent) {
    370         if (mNotificationComponent != null) {
    371             Intent intent = new Intent();
    372             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    373             intent.setComponent(mNotificationComponent);
    374             intent.setAction(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION);
    375 
    376             if (count != null) {
    377                 intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, count);
    378             }
    379 
    380             // Additional information about the voicemail notification beyond the count is only
    381             // present when the count not specified or greater than 0. The value of 0 represents
    382             // clearing the notification, which does not require additional information.
    383             if (count == null || count > 0) {
    384                 if (!TextUtils.isEmpty(number)) {
    385                     intent.putExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER, number);
    386                 }
    387 
    388                 if (pendingIntent != null) {
    389                     intent.putExtra(isSettingsIntent
    390                             ? TelephonyManager.EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
    391                             : TelephonyManager.EXTRA_CALL_VOICEMAIL_INTENT,
    392                             pendingIntent);
    393                 }
    394             }
    395 
    396             mContext.sendBroadcast(intent);
    397             return true;
    398         }
    399 
    400         return false;
    401     }
    402 
    403     /**
    404      * Updates the message call forwarding indicator notification.
    405      *
    406      * @param visible true if there are messages waiting
    407      */
    408     /* package */ void updateCfi(int subId, boolean visible) {
    409         if (DBG) log("updateCfi(): " + visible);
    410         if (visible) {
    411             // If Unconditional Call Forwarding (forward all calls) for VOICE
    412             // is enabled, just show a notification.  We'll default to expanded
    413             // view for now, so the there is less confusion about the icon.  If
    414             // it is deemed too weird to have CF indications as expanded views,
    415             // then we'll flip the flag back.
    416 
    417             // TODO: We may want to take a look to see if the notification can
    418             // display the target to forward calls to.  This will require some
    419             // effort though, since there are multiple layers of messages that
    420             // will need to propagate that information.
    421 
    422             SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
    423             if (subInfo == null) {
    424                 Log.w(LOG_TAG, "Found null subscription info for: " + subId);
    425                 return;
    426             }
    427 
    428             String notificationTitle;
    429             if (mTelephonyManager.getPhoneCount() > 1) {
    430                 notificationTitle = subInfo.getDisplayName().toString();
    431             } else {
    432                 notificationTitle = mContext.getString(R.string.labelCF);
    433             }
    434 
    435             Notification.Builder builder = new Notification.Builder(mContext)
    436                     .setSmallIcon(R.drawable.stat_sys_phone_call_forward)
    437                     .setColor(subInfo.getIconTint())
    438                     .setContentTitle(notificationTitle)
    439                     .setContentText(mContext.getString(R.string.sum_cfu_enabled_indicator))
    440                     .setShowWhen(false)
    441                     .setOngoing(true);
    442 
    443             Intent intent = new Intent(Intent.ACTION_MAIN);
    444             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    445             intent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
    446             SubscriptionInfoHelper.addExtrasToIntent(
    447                     intent, mSubscriptionManager.getActiveSubscriptionInfo(subId));
    448             PendingIntent contentIntent =
    449                     PendingIntent.getActivity(mContext, subId /* requestCode */, intent, 0);
    450 
    451             List<UserInfo> users = mUserManager.getUsers(true);
    452             for (int i = 0; i < users.size(); i++) {
    453                 final UserInfo user = users.get(i);
    454                 if (user.isManagedProfile()) {
    455                     continue;
    456                 }
    457                 UserHandle userHandle = user.getUserHandle();
    458                 builder.setContentIntent(user.isAdmin() ? contentIntent : null);
    459                 mNotificationManager.notifyAsUser(
    460                         Integer.toString(subId) /* tag */,
    461                         CALL_FORWARD_NOTIFICATION,
    462                         builder.build(),
    463                         userHandle);
    464             }
    465         } else {
    466             mNotificationManager.cancelAsUser(
    467                     Integer.toString(subId) /* tag */,
    468                     CALL_FORWARD_NOTIFICATION,
    469                     UserHandle.ALL);
    470         }
    471     }
    472 
    473     /**
    474      * Shows the "data disconnected due to roaming" notification, which
    475      * appears when you lose data connectivity because you're roaming and
    476      * you have the "data roaming" feature turned off.
    477      */
    478     /* package */ void showDataDisconnectedRoaming() {
    479         if (DBG) log("showDataDisconnectedRoaming()...");
    480 
    481         // "Mobile network settings" screen / dialog
    482         Intent intent = new Intent(mContext, com.android.phone.MobileNetworkSettings.class);
    483         PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
    484 
    485         final CharSequence contentText = mContext.getText(R.string.roaming_reenable_message);
    486 
    487         final Notification.Builder builder = new Notification.Builder(mContext)
    488                 .setSmallIcon(android.R.drawable.stat_sys_warning)
    489                 .setContentTitle(mContext.getText(R.string.roaming))
    490                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
    491                 .setContentText(contentText);
    492 
    493         List<UserInfo> users = mUserManager.getUsers(true);
    494         for (int i = 0; i < users.size(); i++) {
    495             final UserInfo user = users.get(i);
    496             if (user.isManagedProfile()) {
    497                 continue;
    498             }
    499             UserHandle userHandle = user.getUserHandle();
    500             builder.setContentIntent(user.isAdmin() ? contentIntent : null);
    501             final Notification notif =
    502                     new Notification.BigTextStyle(builder).bigText(contentText).build();
    503             mNotificationManager.notifyAsUser(
    504                     null /* tag */, DATA_DISCONNECTED_ROAMING_NOTIFICATION, notif, userHandle);
    505         }
    506     }
    507 
    508     /**
    509      * Turns off the "data disconnected due to roaming" notification.
    510      */
    511     /* package */ void hideDataDisconnectedRoaming() {
    512         if (DBG) log("hideDataDisconnectedRoaming()...");
    513         mNotificationManager.cancel(DATA_DISCONNECTED_ROAMING_NOTIFICATION);
    514     }
    515 
    516     /**
    517      * Display the network selection "no service" notification
    518      * @param operator is the numeric operator number
    519      */
    520     private void showNetworkSelection(String operator) {
    521         if (DBG) log("showNetworkSelection(" + operator + ")...");
    522 
    523         Notification.Builder builder = new Notification.Builder(mContext)
    524                 .setSmallIcon(android.R.drawable.stat_sys_warning)
    525                 .setContentTitle(mContext.getString(R.string.notification_network_selection_title))
    526                 .setContentText(
    527                         mContext.getString(R.string.notification_network_selection_text, operator))
    528                 .setShowWhen(false)
    529                 .setOngoing(true);
    530 
    531         // create the target network operators settings intent
    532         Intent intent = new Intent(Intent.ACTION_MAIN);
    533         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    534                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    535         // Use NetworkSetting to handle the selection intent
    536         intent.setComponent(new ComponentName(
    537                 mContext.getString(R.string.network_operator_settings_package),
    538                 mContext.getString(R.string.network_operator_settings_class)));
    539         intent.putExtra(GsmUmtsOptions.EXTRA_SUB_ID, mPhone.getSubId());
    540         PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
    541 
    542         List<UserInfo> users = mUserManager.getUsers(true);
    543         for (int i = 0; i < users.size(); i++) {
    544             final UserInfo user = users.get(i);
    545             if (user.isManagedProfile()) {
    546                 continue;
    547             }
    548             UserHandle userHandle = user.getUserHandle();
    549             builder.setContentIntent(user.isAdmin() ? contentIntent : null);
    550             mNotificationManager.notifyAsUser(
    551                     null /* tag */,
    552                     SELECTED_OPERATOR_FAIL_NOTIFICATION,
    553                     builder.build(),
    554                     userHandle);
    555         }
    556     }
    557 
    558     /**
    559      * Turn off the network selection "no service" notification
    560      */
    561     private void cancelNetworkSelection() {
    562         if (DBG) log("cancelNetworkSelection()...");
    563         mNotificationManager.cancelAsUser(
    564                 null /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION, UserHandle.ALL);
    565     }
    566 
    567     /**
    568      * Update notification about no service of user selected operator
    569      *
    570      * @param serviceState Phone service state
    571      */
    572     void updateNetworkSelection(int serviceState) {
    573         if (TelephonyCapabilities.supportsNetworkSelection(mPhone)) {
    574             int subId = mPhone.getSubId();
    575             if (SubscriptionManager.isValidSubscriptionId(subId)) {
    576                 // get the shared preference of network_selection.
    577                 // empty is auto mode, otherwise it is the operator alpha name
    578                 // in case there is no operator name, check the operator numeric
    579                 SharedPreferences sp =
    580                         PreferenceManager.getDefaultSharedPreferences(mContext);
    581                 String networkSelection =
    582                         sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
    583                 if (TextUtils.isEmpty(networkSelection)) {
    584                     networkSelection =
    585                             sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
    586                 }
    587 
    588                 if (DBG) log("updateNetworkSelection()..." + "state = " +
    589                         serviceState + " new network " + networkSelection);
    590 
    591                 if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
    592                         && !TextUtils.isEmpty(networkSelection)) {
    593                     showNetworkSelection(networkSelection);
    594                     mSelectedUnavailableNotify = true;
    595                 } else {
    596                     if (mSelectedUnavailableNotify) {
    597                         cancelNetworkSelection();
    598                         mSelectedUnavailableNotify = false;
    599                     }
    600                 }
    601             } else {
    602                 if (DBG) log("updateNetworkSelection()..." + "state = " +
    603                         serviceState + " not updating network due to invalid subId " + subId);
    604             }
    605         }
    606     }
    607 
    608     /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
    609         if (mToast != null) {
    610             mToast.cancel();
    611         }
    612 
    613         mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
    614         mToast.show();
    615     }
    616 
    617     private void log(String msg) {
    618         Log.d(LOG_TAG, msg);
    619     }
    620 }
    621