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