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