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.res.Resources;
33 import android.net.Uri;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.os.PersistableBundle;
37 import android.os.SystemClock;
38 import android.os.SystemProperties;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.preference.PreferenceManager;
42 import android.provider.ContactsContract.PhoneLookup;
43 import android.provider.Settings;
44 import android.telecom.PhoneAccount;
45 import android.telecom.PhoneAccountHandle;
46 import android.telecom.TelecomManager;
47 import android.telephony.CarrierConfigManager;
48 import android.telephony.PhoneNumberUtils;
49 import android.telephony.ServiceState;
50 import android.telephony.SubscriptionInfo;
51 import android.telephony.SubscriptionManager;
52 import android.telephony.TelephonyManager;
53 import android.text.TextUtils;
54 import android.util.ArrayMap;
55 import android.util.Log;
56 import android.util.SparseArray;
57 import android.widget.Toast;
58 
59 import com.android.internal.telephony.Phone;
60 import com.android.internal.telephony.PhoneFactory;
61 import com.android.internal.telephony.TelephonyCapabilities;
62 import com.android.internal.telephony.util.NotificationChannelController;
63 import com.android.phone.settings.VoicemailSettingsActivity;
64 
65 import java.util.ArrayList;
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,
359                             PendingIntent.FLAG_IMMUTABLE);
360 
361             Resources res = mContext.getResources();
362             PersistableBundle carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
363                     subId);
364             Notification.Builder builder = new Notification.Builder(mContext);
365             builder.setSmallIcon(resId)
366                     .setWhen(System.currentTimeMillis())
367                     .setColor(subInfo.getIconTint())
368                     .setContentTitle(notificationTitle)
369                     .setContentText(notificationText)
370                     .setContentIntent(pendingIntent)
371                     .setColor(res.getColor(R.color.dialer_theme_color))
372                     .setOngoing(carrierConfig.getBoolean(
373                             CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL))
374                     .setChannelId(NotificationChannelController.CHANNEL_ID_VOICE_MAIL)
375                     .setOnlyAlertOnce(isRefresh);
376 
377             final Notification notification = builder.build();
378             List<UserHandle> users = getUsersExcludeDying();
379             for (UserHandle userHandle : users) {
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<UserHandle> users = getUsersExcludeDying();
395             for (UserHandle userHandle : users) {
396                 if (!hasUserRestriction(
397                         UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
398                         && !mUserManager.isManagedProfile(userHandle.getIdentifier())) {
399                     if (!maybeSendVoicemailNotificationUsingDefaultDialer(phone, 0, null, null,
400                             false, userHandle, isRefresh)) {
401                         cancelAsUser(
402                                 Integer.toString(subId) /* tag */,
403                                 VOICEMAIL_NOTIFICATION,
404                                 userHandle);
405                     }
406                 }
407             }
408         }
409     }
410 
getUsersExcludeDying()411     private List<UserHandle> getUsersExcludeDying() {
412         long[] serialNumbersOfUsers =
413                 mUserManager.getSerialNumbersOfUsers(/* excludeDying= */ true);
414         List<UserHandle> users = new ArrayList<>(serialNumbersOfUsers.length);
415         for (long serialNumber : serialNumbersOfUsers) {
416             users.add(mUserManager.getUserForSerialNumber(serialNumber));
417         }
418         return users;
419     }
420 
hasUserRestriction(String restrictionKey, UserHandle userHandle)421     private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
422         final List<UserManager.EnforcingUser> sources = mUserManager
423                 .getUserRestrictionSources(restrictionKey, userHandle);
424         return (sources != null && !sources.isEmpty());
425     }
426 
427     /**
428      * Sends a broadcast with the voicemail notification information to the default dialer. This
429      * method is also used to indicate to the default dialer when to clear the
430      * notification. A pending intent can be passed to the default dialer to indicate an action to
431      * be taken as it would by a notification produced in this class.
432      * @param phone The phone the notification is sent from
433      * @param count The number of pending voicemail messages to indicate on the notification. A
434      *              Value of 0 is passed here to indicate that the notification should be cleared.
435      * @param number The voicemail phone number if specified.
436      * @param pendingIntent The intent that should be passed as the action to be taken.
437      * @param isSettingsIntent {@code true} to indicate the pending intent is to launch settings.
438      *                         otherwise, {@code false} to indicate the intent launches voicemail.
439      * @param userHandle The user to receive the notification. Each user can have their own default
440      *                   dialer.
441      * @return {@code true} if the default was notified of the notification.
442      */
maybeSendVoicemailNotificationUsingDefaultDialer(Phone phone, Integer count, String number, PendingIntent pendingIntent, boolean isSettingsIntent, UserHandle userHandle, boolean isRefresh)443     private boolean maybeSendVoicemailNotificationUsingDefaultDialer(Phone phone, Integer count,
444             String number, PendingIntent pendingIntent, boolean isSettingsIntent,
445             UserHandle userHandle, boolean isRefresh) {
446 
447         if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
448             Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle);
449             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
450             intent.setAction(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION);
451             intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE,
452                     PhoneUtils.makePstnPhoneAccountHandle(phone));
453             intent.putExtra(TelephonyManager.EXTRA_IS_REFRESH, isRefresh);
454             if (count != null) {
455                 intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, count);
456             }
457 
458             // Additional information about the voicemail notification beyond the count is only
459             // present when the count not specified or greater than 0. The value of 0 represents
460             // clearing the notification, which does not require additional information.
461             if (count == null || count > 0) {
462                 if (!TextUtils.isEmpty(number)) {
463                     intent.putExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER, number);
464                 }
465 
466                 if (pendingIntent != null) {
467                     intent.putExtra(isSettingsIntent
468                             ? TelephonyManager.EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
469                             : TelephonyManager.EXTRA_CALL_VOICEMAIL_INTENT,
470                             pendingIntent);
471                 }
472             }
473             mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE);
474             return true;
475         }
476 
477         return false;
478     }
479 
getShowVoicemailIntentForDefaultDialer(UserHandle userHandle)480     private Intent getShowVoicemailIntentForDefaultDialer(UserHandle userHandle) {
481         String dialerPackage = mContext.getSystemService(TelecomManager.class)
482                 .getDefaultDialerPackage(userHandle);
483         return new Intent(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION)
484                 .setPackage(dialerPackage);
485     }
486 
shouldManageNotificationThroughDefaultDialer(UserHandle userHandle)487     private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) {
488         Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle);
489         if (intent == null) {
490             return false;
491         }
492 
493         List<ResolveInfo> receivers = mContext.getPackageManager()
494                 .queryBroadcastReceivers(intent, 0);
495         return receivers.size() > 0;
496     }
497 
498     /**
499      * Updates the message call forwarding indicator notification.
500      *
501      * @param visible true if call forwarding enabled
502      */
503 
updateCfi(int subId, boolean visible)504      /* package */ void updateCfi(int subId, boolean visible) {
505         updateCfi(subId, visible, false /* isRefresh */);
506     }
507 
508     /**
509      * Updates the message call forwarding indicator notification.
510      *
511      * @param visible true if call forwarding enabled
512      */
updateCfi(int subId, boolean visible, boolean isRefresh)513     /* package */ void updateCfi(int subId, boolean visible, boolean isRefresh) {
514         logi("updateCfi: subId= " + subId + ", visible=" + (visible ? "Y" : "N"));
515         if (visible) {
516             // If Unconditional Call Forwarding (forward all calls) for VOICE
517             // is enabled, just show a notification.  We'll default to expanded
518             // view for now, so the there is less confusion about the icon.  If
519             // it is deemed too weird to have CF indications as expanded views,
520             // then we'll flip the flag back.
521 
522             // TODO: We may want to take a look to see if the notification can
523             // display the target to forward calls to.  This will require some
524             // effort though, since there are multiple layers of messages that
525             // will need to propagate that information.
526 
527             SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
528             if (subInfo == null) {
529                 Log.w(LOG_TAG, "Found null subscription info for: " + subId);
530                 return;
531             }
532 
533             String notificationTitle;
534             int resId = R.drawable.stat_sys_phone_call_forward;
535             if (mTelephonyManager.getPhoneCount() > 1) {
536                 int slotId = SubscriptionManager.getSlotIndex(subId);
537                 resId = (slotId == 0) ? R.drawable.stat_sys_phone_call_forward_sub1
538                         : R.drawable.stat_sys_phone_call_forward_sub2;
539                 notificationTitle = subInfo.getDisplayName().toString();
540             } else {
541                 notificationTitle = mContext.getString(R.string.labelCF);
542             }
543 
544             Notification.Builder builder = new Notification.Builder(mContext)
545                     .setSmallIcon(resId)
546                     .setColor(subInfo.getIconTint())
547                     .setContentTitle(notificationTitle)
548                     .setContentText(mContext.getString(R.string.sum_cfu_enabled_indicator))
549                     .setShowWhen(false)
550                     .setOngoing(true)
551                     .setChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD)
552                     .setOnlyAlertOnce(isRefresh);
553 
554             Intent intent = new Intent(Intent.ACTION_MAIN);
555             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
556             intent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
557             SubscriptionInfoHelper.addExtrasToIntent(
558                     intent, mSubscriptionManager.getActiveSubscriptionInfo(subId));
559             builder.setContentIntent(PendingIntent.getActivity(mContext, subId /* requestCode */,
560                     intent, PendingIntent.FLAG_IMMUTABLE));
561             notifyAsUser(
562                     Integer.toString(subId) /* tag */,
563                     CALL_FORWARD_NOTIFICATION,
564                     builder.build(),
565                     UserHandle.ALL);
566         } else {
567             List<UserHandle> users = getUsersExcludeDying();
568             for (UserHandle user : users) {
569                 if (mUserManager.isManagedProfile(user.getIdentifier())) {
570                     continue;
571                 }
572                 cancelAsUser(
573                         Integer.toString(subId) /* tag */,
574                         CALL_FORWARD_NOTIFICATION,
575                         user);
576             }
577         }
578     }
579 
580     /**
581      * Shows either:
582      * 1) the "Data roaming is on" notification, which
583      * appears when you're roaming and you have the "data roaming" feature turned on for the
584      * given {@code subId}.
585      * or
586      * 2) the "data disconnected due to roaming" notification, which
587      * appears when you lose data connectivity because you're roaming and
588      * you have the "data roaming" feature turned off for the given {@code subId}.
589      * @param subId which subscription it's notifying about.
590      * @param roamingOn whether currently roaming is on or off. If true, we show notification
591      *                  1) above; else we show notification 2).
592      */
showDataRoamingNotification(int subId, boolean roamingOn)593     /* package */ void showDataRoamingNotification(int subId, boolean roamingOn) {
594         if (DBG) {
595             log("showDataRoamingNotification() roaming " + (roamingOn ? "on" : "off")
596                     + " on subId " + subId);
597         }
598 
599         // "Mobile network settings" screen / dialog
600         Intent intent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
601         intent.putExtra(Settings.EXTRA_SUB_ID, subId);
602         PendingIntent contentIntent = PendingIntent.getActivity(
603                 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE);
604 
605         CharSequence contentTitle = mContext.getText(roamingOn
606                 ? R.string.roaming_on_notification_title
607                 : R.string.roaming_notification_title);
608         CharSequence contentText = mContext.getText(roamingOn
609                 ? R.string.roaming_enabled_message
610                 : R.string.roaming_reenable_message);
611 
612         final Notification.Builder builder = new Notification.Builder(mContext)
613                 .setSmallIcon(android.R.drawable.stat_sys_warning)
614                 .setContentTitle(contentTitle)
615                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
616                 .setContentText(contentText)
617                 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
618                 .setContentIntent(contentIntent);
619         final Notification notif =
620                 new Notification.BigTextStyle(builder).bigText(contentText).build();
621         notifyAsUser(null /* tag */, DATA_ROAMING_NOTIFICATION, notif, UserHandle.ALL);
622     }
623 
notifyAsUser(String tag, int id, Notification notification, UserHandle user)624     private void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {
625         try {
626             Context contextForUser =
627                     mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
628             NotificationManager notificationManager =
629                     (NotificationManager) contextForUser.getSystemService(
630                             Context.NOTIFICATION_SERVICE);
631             notificationManager.notify(tag, id, notification);
632         } catch (PackageManager.NameNotFoundException e) {
633             Log.e(LOG_TAG, "unable to notify for user " + user);
634             e.printStackTrace();
635         }
636     }
637 
cancelAsUser(String tag, int id, UserHandle user)638     private void cancelAsUser(String tag, int id, UserHandle user) {
639         try {
640             Context contextForUser =
641                     mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
642             NotificationManager notificationManager =
643                     (NotificationManager) contextForUser.getSystemService(
644                             Context.NOTIFICATION_SERVICE);
645             notificationManager.cancel(tag, id);
646         } catch (PackageManager.NameNotFoundException e) {
647             Log.e(LOG_TAG, "unable to cancel for user " + user);
648             e.printStackTrace();
649         }
650     }
651 
652     /**
653      * Turns off the "data disconnected due to roaming" or "Data roaming is on" notification.
654      */
hideDataRoamingNotification()655     /* package */ void hideDataRoamingNotification() {
656         if (DBG) log("hideDataRoamingNotification()...");
657         cancelAsUser(null, DATA_ROAMING_NOTIFICATION, UserHandle.ALL);
658     }
659 
660     /**
661      * Shows the "Limited SIM functionality" warning notification, which appears when using a
662      * special carrier under dual sim. limited function applies for DSDS in general when two SIM
663      * cards share a single radio, thus the voice & data maybe impaired under certain scenarios.
664      */
showLimitedSimFunctionWarningNotification(int subId, @Nullable String carrierName)665     public void showLimitedSimFunctionWarningNotification(int subId, @Nullable String carrierName) {
666         if (DBG) log("showLimitedSimFunctionWarningNotification carrier: " + carrierName
667                 + " subId: " + subId);
668         if (mLimitedSimFunctionNotify.contains(subId)) {
669             // handle the case that user swipe the notification but condition triggers
670             // frequently which cause the same notification consistently displayed.
671             if (DBG) log("showLimitedSimFunctionWarningNotification, "
672                     + "not display again if already displayed");
673             return;
674         }
675         // Navigate to "Network Selection Settings" which list all subscriptions.
676         PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,
677                 new Intent(ACTION_MOBILE_NETWORK_LIST), PendingIntent.FLAG_IMMUTABLE);
678         // Display phone number from the other sub
679         String line1Num = null;
680         SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
681             Context.TELEPHONY_SUBSCRIPTION_SERVICE);
682         List<SubscriptionInfo> subList = subMgr.getActiveSubscriptionInfoList(false);
683         for (SubscriptionInfo sub : subList) {
684             if (sub.getSubscriptionId() != subId) {
685                 line1Num = mTelephonyManager.getLine1Number(sub.getSubscriptionId());
686             }
687         }
688         final CharSequence contentText = TextUtils.isEmpty(line1Num) ?
689             String.format(mContext.getText(
690                 R.string.limited_sim_function_notification_message).toString(), carrierName) :
691             String.format(mContext.getText(
692                 R.string.limited_sim_function_with_phone_num_notification_message).toString(),
693                 carrierName, line1Num);
694         final Notification.Builder builder = new Notification.Builder(mContext)
695                 .setSmallIcon(R.drawable.ic_sim_card)
696                 .setContentTitle(mContext.getText(
697                         R.string.limited_sim_function_notification_title))
698                 .setContentText(contentText)
699                 .setOnlyAlertOnce(true)
700                 .setOngoing(true)
701                 .setChannelId(NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY)
702                 .setContentIntent(contentIntent);
703         final Notification notification = new Notification.BigTextStyle(builder).bigText(
704                 contentText).build();
705 
706         notifyAsUser(Integer.toString(subId),
707                 LIMITED_SIM_FUNCTION_NOTIFICATION,
708                 notification, UserHandle.ALL);
709         mLimitedSimFunctionNotify.add(subId);
710     }
711 
712     /**
713      * Dismiss the "Limited SIM functionality" warning notification for the given subId.
714      */
dismissLimitedSimFunctionWarningNotification(int subId)715     public void dismissLimitedSimFunctionWarningNotification(int subId) {
716         if (DBG) log("dismissLimitedSimFunctionWarningNotification subId: " + subId);
717         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
718             // dismiss all notifications
719             for (int id : mLimitedSimFunctionNotify) {
720                 cancelAsUser(Integer.toString(id),
721                         LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
722             }
723             mLimitedSimFunctionNotify.clear();
724         } else if (mLimitedSimFunctionNotify.contains(subId)) {
725             cancelAsUser(Integer.toString(subId),
726                     LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
727             mLimitedSimFunctionNotify.remove(subId);
728         }
729     }
730 
731     /**
732      * Dismiss the "Limited SIM functionality" warning notification for all inactive subscriptions.
733      */
dismissLimitedSimFunctionWarningNotificationForInactiveSubs()734     public void dismissLimitedSimFunctionWarningNotificationForInactiveSubs() {
735         if (DBG) log("dismissLimitedSimFunctionWarningNotificationForInactiveSubs");
736         // dismiss notification for inactive subscriptions.
737         // handle the corner case that SIM change by SIM refresh doesn't clear the notification
738         // from the old SIM if both old & new SIM configured to display the notification.
739         mLimitedSimFunctionNotify.removeIf(id -> {
740             if (!mSubscriptionManager.isActiveSubId(id)) {
741                 cancelAsUser(Integer.toString(id),
742                         LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL);
743                 return true;
744             }
745             return false;
746         });
747     }
748 
749     /**
750      * Display the network selection "no service" notification
751      * @param operator is the numeric operator number
752      * @param subId is the subscription ID
753      */
showNetworkSelection(String operator, int subId)754     private void showNetworkSelection(String operator, int subId) {
755         if (DBG) log("showNetworkSelection(" + operator + ")...");
756 
757         if (!TextUtils.isEmpty(operator)) {
758             operator = String.format(" (%s)", operator);
759         }
760         Notification.Builder builder = new Notification.Builder(mContext)
761                 .setSmallIcon(android.R.drawable.stat_sys_warning)
762                 .setContentTitle(mContext.getString(R.string.notification_network_selection_title))
763                 .setContentText(
764                         mContext.getString(R.string.notification_network_selection_text, operator))
765                 .setShowWhen(false)
766                 .setOngoing(true)
767                 .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
768 
769         // create the target network operators settings intent
770         Intent intent = new Intent(Intent.ACTION_MAIN);
771         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
772                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
773         // Use MobileNetworkSettings to handle the selection intent
774         intent.setComponent(new ComponentName(
775                 mContext.getString(R.string.mobile_network_settings_package),
776                 mContext.getString(R.string.mobile_network_settings_class)));
777         intent.putExtra(Settings.EXTRA_SUB_ID, subId);
778         builder.setContentIntent(
779                 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE));
780         notifyAsUser(
781                 Integer.toString(subId) /* tag */,
782                 SELECTED_OPERATOR_FAIL_NOTIFICATION,
783                 builder.build(),
784                 UserHandle.ALL);
785         mSelectedUnavailableNotify.put(subId, true);
786     }
787 
788     /**
789      * Turn off the network selection "no service" notification
790      */
cancelNetworkSelection(int subId)791     private void cancelNetworkSelection(int subId) {
792         if (DBG) log("cancelNetworkSelection()...");
793         cancelAsUser(
794                 Integer.toString(subId) /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION,
795                 UserHandle.ALL);
796     }
797 
798     /**
799      * Update notification about no service of user selected operator
800      *
801      * @param serviceState Phone service state
802      * @param subId The subscription ID
803      */
updateNetworkSelection(int serviceState, int subId)804     void updateNetworkSelection(int serviceState, int subId) {
805         int phoneId = SubscriptionManager.getPhoneId(subId);
806         Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
807                 PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
808         if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
809             if (SubscriptionManager.isValidSubscriptionId(subId)) {
810                 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
811                 String selectedNetworkOperatorName =
812                         sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
813                 // get the shared preference of network_selection.
814                 // empty is auto mode, otherwise it is the operator alpha name
815                 // in case there is no operator name, check the operator numeric
816                 if (TextUtils.isEmpty(selectedNetworkOperatorName)) {
817                     selectedNetworkOperatorName =
818                             sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
819                 }
820                 boolean isManualSelection;
821                 // if restoring manual selection is controlled by framework, then get network
822                 // selection from shared preference, otherwise get from real network indicators.
823                 boolean restoreSelection = !mContext.getResources().getBoolean(
824                         com.android.internal.R.bool.skip_restoring_network_selection);
825                 if (restoreSelection) {
826                     isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName);
827                 } else {
828                     isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection();
829                 }
830 
831                 if (DBG) {
832                     log("updateNetworkSelection()..." + "state = " + serviceState + " new network "
833                             + (isManualSelection ? selectedNetworkOperatorName : ""));
834                 }
835 
836                 if (isManualSelection) {
837                     mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
838                     shouldShowNotification(serviceState, subId);
839                 } else {
840                     dismissNetworkSelectionNotification(subId);
841                     clearUpNetworkSelectionNotificationParam(subId);
842                 }
843             } else {
844                 if (DBG) log("updateNetworkSelection()..." + "state = " +
845                         serviceState + " not updating network due to invalid subId " + subId);
846                 dismissNetworkSelectionNotificationForInactiveSubId();
847             }
848         }
849     }
850 
dismissNetworkSelectionNotification(int subId)851     private void dismissNetworkSelectionNotification(int subId) {
852         if (mSelectedUnavailableNotify.get(subId, false)) {
853             cancelNetworkSelection(subId);
854             mSelectedUnavailableNotify.remove(subId);
855         }
856     }
857 
dismissNetworkSelectionNotificationForInactiveSubId()858     private void dismissNetworkSelectionNotificationForInactiveSubId() {
859         for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
860             int subId = mSelectedUnavailableNotify.keyAt(i);
861             if (!mSubscriptionManager.isActiveSubId(subId)) {
862                 dismissNetworkSelectionNotification(subId);
863                 clearUpNetworkSelectionNotificationParam(subId);
864             }
865         }
866     }
867 
postTransientNotification(int notifyId, CharSequence msg)868     /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
869         if (mToast != null) {
870             mToast.cancel();
871         }
872 
873         mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
874         mToast.show();
875     }
876 
log(String msg)877     private void log(String msg) {
878         Log.d(LOG_TAG, msg);
879     }
880 
logi(String msg)881     private void logi(String msg) {
882         Log.i(LOG_TAG, msg);
883     }
884 
885     /**
886      * In case network selection notification shows up repeatedly under
887      * unstable network condition. The logic is to check whether or not
888      * the service state keeps in no service condition for at least
889      * {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}.
890      * And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times.
891      * To avoid the notification showing up for the momentary state.
892      */
shouldShowNotification(int serviceState, int subId)893     private void shouldShowNotification(int serviceState, int subId) {
894         if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
895             if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
896                     != ServiceState.STATE_OUT_OF_SERVICE) {
897                 mOOSTimestamp.put(subId, getTimeStamp());
898             }
899             if ((getTimeStamp() - mOOSTimestamp.get(subId, 0L)
900                     >= NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS)
901                     || mPendingEventCounter.get(subId, 0)
902                     > NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES) {
903                 showNetworkSelection(mSelectedNetworkOperatorName.get(subId), subId);
904                 clearUpNetworkSelectionNotificationParam(subId);
905             } else {
906                 startPendingNetworkSelectionNotification(subId);
907             }
908         } else {
909             dismissNetworkSelectionNotification(subId);
910         }
911         mPreviousServiceState.put(subId, serviceState);
912         if (DBG) {
913             log("shouldShowNotification()..." + " subId = " + subId
914                     + " serviceState = " + serviceState
915                     + " mOOSTimestamp = " + mOOSTimestamp
916                     + " mPendingEventCounter = " + mPendingEventCounter);
917         }
918     }
919 
startPendingNetworkSelectionNotification(int subId)920     private void startPendingNetworkSelectionNotification(int subId) {
921         if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
922             if (DBG) {
923                 log("startPendingNetworkSelectionNotification: subId = " + subId);
924             }
925             mHandler.sendMessageDelayed(
926                     mHandler.obtainMessage(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId),
927                     NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS);
928             mPendingEventCounter.put(subId, mPendingEventCounter.get(subId, 0) + 1);
929         }
930     }
931 
clearUpNetworkSelectionNotificationParam(int subId)932     private void clearUpNetworkSelectionNotificationParam(int subId) {
933         if (mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
934             mHandler.removeMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId);
935         }
936         mPreviousServiceState.remove(subId);
937         mOOSTimestamp.remove(subId);
938         mPendingEventCounter.remove(subId);
939         mSelectedNetworkOperatorName.remove(subId);
940     }
941 
getTimeStamp()942     private static long getTimeStamp() {
943         return SystemClock.elapsedRealtime();
944     }
945 }
946