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