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