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