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