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