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