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