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