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