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