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