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