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