1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.phone; 18 19 import static android.Manifest.permission.READ_PHONE_STATE; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.BroadcastOptions; 24 import android.app.Notification; 25 import android.app.NotificationManager; 26 import android.app.PendingIntent; 27 import android.app.StatusBarManager; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.SharedPreferences; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.res.Resources; 35 import android.net.Uri; 36 import android.os.Handler; 37 import android.os.Message; 38 import android.os.PersistableBundle; 39 import android.os.SystemClock; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.preference.PreferenceManager; 44 import android.provider.ContactsContract.PhoneLookup; 45 import android.provider.Settings; 46 import android.telecom.PhoneAccount; 47 import android.telecom.PhoneAccountHandle; 48 import android.telecom.TelecomManager; 49 import android.telephony.CarrierConfigManager; 50 import android.telephony.PhoneNumberUtils; 51 import android.telephony.RadioAccessFamily; 52 import android.telephony.ServiceState; 53 import android.telephony.SubscriptionInfo; 54 import android.telephony.SubscriptionManager; 55 import android.telephony.TelephonyManager; 56 import android.text.TextUtils; 57 import android.util.ArrayMap; 58 import android.util.Log; 59 import android.util.SparseArray; 60 import android.widget.Toast; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.telephony.Phone; 64 import com.android.internal.telephony.PhoneFactory; 65 import com.android.internal.telephony.RILConstants; 66 import com.android.internal.telephony.TelephonyCapabilities; 67 import com.android.internal.telephony.flags.FeatureFlags; 68 import com.android.internal.telephony.flags.FeatureFlagsImpl; 69 import com.android.internal.telephony.util.NotificationChannelController; 70 import com.android.phone.settings.VoicemailSettingsActivity; 71 72 import java.util.ArrayList; 73 import java.util.HashSet; 74 import java.util.Iterator; 75 import java.util.List; 76 import java.util.Objects; 77 import java.util.Set; 78 79 /** 80 * NotificationManager-related utility code for the Phone app. 81 * 82 * This is a singleton object which acts as the interface to the 83 * framework's NotificationManager, and is used to display status bar 84 * icons and control other status bar-related behavior. 85 * 86 * @see PhoneGlobals.notificationMgr 87 */ 88 public class NotificationMgr { 89 private static final String LOG_TAG = NotificationMgr.class.getSimpleName(); 90 private static final boolean DBG = 91 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 92 // Do not check in with VDBG = true, since that may write PII to the system log. 93 private static final boolean VDBG = false; 94 95 private static final String MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX = 96 "mwi_should_check_vvm_configuration_state_"; 97 98 // notification types 99 static final int MMI_NOTIFICATION = 1; 100 static final int NETWORK_SELECTION_NOTIFICATION = 2; 101 static final int VOICEMAIL_NOTIFICATION = 3; 102 static final int CALL_FORWARD_NOTIFICATION = 4; 103 static final int DATA_ROAMING_NOTIFICATION = 5; 104 static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6; 105 static final int LIMITED_SIM_FUNCTION_NOTIFICATION = 7; 106 107 // Event for network selection notification. 108 private static final int EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION = 1; 109 110 private static final long NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS = 10000L; 111 private static final int NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES = 10; 112 113 private static final int STATE_UNKNOWN_SERVICE = -1; 114 115 private static final String ACTION_MOBILE_NETWORK_LIST = "android.settings.MOBILE_NETWORK_LIST"; 116 117 /** 118 * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background 119 * service to do VVM processing. 120 */ 121 private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000L; 122 123 /** The singleton NotificationMgr instance. */ 124 private static NotificationMgr sInstance; 125 126 private PhoneGlobals mApp; 127 128 private Context mContext; 129 private StatusBarManager mStatusBarManager; 130 private UserManager mUserManager; 131 private Toast mToast; 132 private SubscriptionManager mSubscriptionManager; 133 private TelecomManager mTelecomManager; 134 private TelephonyManager mTelephonyManager; 135 136 // used to track the notification of selected network unavailable, per subscription id. 137 private SparseArray<Boolean> mSelectedUnavailableNotify = new SparseArray<>(); 138 139 // used to track the notification of limited sim function under dual sim, per subscription id. 140 private Set<Integer> mLimitedSimFunctionNotify = new HashSet<>(); 141 142 // used to track whether the message waiting indicator is visible, per subscription id. 143 private ArrayMap<Integer, Boolean> mMwiVisible = new ArrayMap<Integer, Boolean>(); 144 // used to track the last broadcast sent to the dialer about the MWI, per sub id. 145 private ArrayMap<Integer, Integer> mLastMwiCountSent = new ArrayMap<Integer, Integer>(); 146 147 // those flags are used to track whether to show network selection notification or not. 148 private SparseArray<Integer> mPreviousServiceState = new SparseArray<>(); 149 private SparseArray<Long> mOOSTimestamp = new SparseArray<>(); 150 private SparseArray<Integer> mPendingEventCounter = new SparseArray<>(); 151 // maps each subId to selected network operator name. 152 private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>(); 153 154 // feature flags 155 private final FeatureFlags mFeatureFlags; 156 157 private final Handler mHandler = new Handler() { 158 @Override 159 public void handleMessage(Message msg) { 160 switch (msg.what) { 161 case EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION: 162 int subId = (int) msg.obj; 163 TelephonyManager telephonyManager = 164 mTelephonyManager.createForSubscriptionId(subId); 165 if (telephonyManager.getServiceState() != null) { 166 shouldShowNotification(telephonyManager.getServiceState().getState(), 167 subId); 168 } 169 break; 170 } 171 } 172 }; 173 174 /** 175 * Private constructor (this is a singleton). 176 * @see #init(PhoneGlobals) 177 */ 178 @VisibleForTesting NotificationMgr(PhoneGlobals app)179 /* package */ NotificationMgr(PhoneGlobals app) { 180 mApp = app; 181 mContext = app; 182 mStatusBarManager = 183 (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE); 184 mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE); 185 mSubscriptionManager = SubscriptionManager.from(mContext); 186 mTelecomManager = app.getSystemService(TelecomManager.class); 187 mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE); 188 mFeatureFlags = new FeatureFlagsImpl(); 189 } 190 191 /** 192 * Initialize the singleton NotificationMgr instance. 193 * 194 * This is only done once, at startup, from PhoneApp.onCreate(). 195 * From then on, the NotificationMgr instance is available via the 196 * PhoneApp's public "notificationMgr" field, which is why there's no 197 * getInstance() method here. 198 */ init(PhoneGlobals app)199 /* package */ static NotificationMgr init(PhoneGlobals app) { 200 synchronized (NotificationMgr.class) { 201 if (sInstance == null) { 202 sInstance = new NotificationMgr(app); 203 } else { 204 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 205 } 206 return sInstance; 207 } 208 } 209 210 /** The projection to use when querying the phones table */ 211 static final String[] PHONES_PROJECTION = new String[] { 212 PhoneLookup.NUMBER, 213 PhoneLookup.DISPLAY_NAME, 214 PhoneLookup._ID 215 }; 216 217 /** 218 * Re-creates the message waiting indicator (voicemail) notification if it is showing. Used to 219 * refresh the voicemail intent on the indicator when the user changes it via the voicemail 220 * settings screen. The voicemail notification sound is suppressed. 221 * 222 * @param subId The subscription Id. 223 */ refreshMwi(int subId)224 /* package */ void refreshMwi(int subId) { 225 // In a single-sim device, subId can be -1 which means "no sub id". In this case we will 226 // reference the single subid stored in the mMwiVisible map. 227 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 228 if (mMwiVisible.keySet().size() == 1) { 229 Set<Integer> keySet = mMwiVisible.keySet(); 230 Iterator<Integer> keyIt = keySet.iterator(); 231 if (!keyIt.hasNext()) { 232 return; 233 } 234 subId = keyIt.next(); 235 } 236 } 237 if (mMwiVisible.containsKey(subId)) { 238 boolean mwiVisible = mMwiVisible.get(subId); 239 if (mwiVisible) { 240 mApp.notifier.updatePhoneStateListeners(true); 241 } 242 } 243 } 244 setShouldCheckVisualVoicemailConfigurationForMwi(int subId, boolean enabled)245 public void setShouldCheckVisualVoicemailConfigurationForMwi(int subId, boolean enabled) { 246 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 247 Log.e(LOG_TAG, "setShouldCheckVisualVoicemailConfigurationForMwi: invalid subId" 248 + subId); 249 return; 250 } 251 252 PreferenceManager.getDefaultSharedPreferences(mContext).edit() 253 .putBoolean(MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX + subId, enabled) 254 .apply(); 255 } 256 shouldCheckVisualVoicemailConfigurationForMwi(int subId)257 private boolean shouldCheckVisualVoicemailConfigurationForMwi(int subId) { 258 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 259 Log.e(LOG_TAG, "shouldCheckVisualVoicemailConfigurationForMwi: invalid subId" + subId); 260 return true; 261 } 262 return PreferenceManager 263 .getDefaultSharedPreferences(mContext) 264 .getBoolean(MWI_SHOULD_CHECK_VVM_CONFIGURATION_KEY_PREFIX + subId, true); 265 } 266 /** 267 * Updates the message waiting indicator (voicemail) notification. 268 * 269 * @param visible true if there are messages waiting 270 */ updateMwi(int subId, boolean visible)271 /* package */ void updateMwi(int subId, boolean visible) { 272 updateMwi(subId, visible, false /* isRefresh */); 273 } 274 275 /** 276 * Updates the message waiting indicator (voicemail) notification. 277 * See also {@link CallNotifier#updatePhoneStateListeners(boolean)} for more background. 278 * Okay, lets get started; time for a code adventure. 279 * This method (unfortunately) serves double-duty for updating the message waiting indicator 280 * when either: the "subscription info" changes (could be due to the carrier name loading, the 281 * current phase of the moon, etc) -- this is considered a "refresh"; or due to a direct signal 282 * from the network that indeed the voicemail indicator has changed state. Note that although 283 * {@link android.telephony.ims.feature.MmTelFeature.Listener#onVoiceMessageCountUpdate(int)} 284 * gives us an actual COUNT of the messages, that information is unfortunately lost in the 285 * current design -- that's a design issue left for another day. Note; it is ALSO possible to 286 * get updates through SMS via {@code GsmInboundSmsHandler#updateMessageWaitingIndicator(int)}, 287 * and that seems to generally not include a count. 288 * <p> 289 * There are two ways the message waiting indicator can be notified to the user; either directly 290 * here by posting a notification, or via the default dialer. 291 * <p> 292 * The {@code isRefresh} == false case is pretty intuitive. The network told us something 293 * changed, so we should notify the user. 294 * <p> 295 * The {@code isRefresh} == true case is unfortunately not very intuitive. While the device is 296 * booting up, we'll actually get a callback from the ImsService telling us the state of the 297 * MWI indication, BUT at that point in time it's possible the sim records won't have loaded so 298 * code below will return early -- the voicemail notification is supposed to include the user's 299 * voicemail number, so we have to defer posting the notification or notifying the dialer until 300 * later on. That's where the refreshes are handy; once the voicemail number is known, we'll 301 * get a refresh and have an opportunity to notify the user at this point. 302 * @param subId the subId to update where {@code isRefresh} is false, 303 * or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if {@code isRefresh} is 304 * true. 305 * @param visible {@code true} if there are messages waiting, or {@code false} if there are not. 306 * @param isRefresh {@code true} if this is a refresh triggered by a subscription change, 307 * {@code false} if this is an update based on actual network 308 * signalling. 309 */ updateMwi(int subId, boolean visible, boolean isRefresh)310 void updateMwi(int subId, boolean visible, boolean isRefresh) { 311 if (!PhoneGlobals.sVoiceCapable) { 312 // Do not show the message waiting indicator on devices which are not voice capable. 313 // These events *should* be blocked at the telephony layer for such devices. 314 Log.w(LOG_TAG, "Called updateMwi() on non-voice-capable device! Ignoring..."); 315 return; 316 } 317 318 Phone phone = PhoneGlobals.getPhone(subId); 319 // -- 320 // Note: This is all done just so that we can have a better log of what is going on here. 321 // The state changes are unfortunately not so intuitive. 322 boolean wasPrevStateKnown = mMwiVisible.containsKey(subId); 323 boolean wasVisible = wasPrevStateKnown && mMwiVisible.get(subId); 324 mMwiVisible.put(subId, visible); 325 boolean mwiStateChanged = !wasPrevStateKnown || wasVisible != visible; 326 // -- 327 328 Log.i(LOG_TAG, "updateMwi(): subId:" + subId + " isRefresh:" + isRefresh + " state:" 329 + (wasPrevStateKnown ? (wasVisible ? "Y" : "N") : "unset") + "->" 330 + (visible ? "Y" : "N") 331 + " (changed:" + (mwiStateChanged ? "Y" : "N") + ")"); 332 333 if (visible) { 334 if (phone == null) { 335 Log.w(LOG_TAG, "Found null phone for: " + subId); 336 return; 337 } 338 339 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId); 340 if (subInfo == null) { 341 Log.w(LOG_TAG, "Found null subscription info for: " + subId); 342 return; 343 } 344 345 int resId = android.R.drawable.stat_notify_voicemail; 346 if (mTelephonyManager.getPhoneCount() > 1) { 347 resId = (phone.getPhoneId() == 0) ? R.drawable.stat_notify_voicemail_sub1 348 : R.drawable.stat_notify_voicemail_sub2; 349 } 350 351 // This Notification can get a lot fancier once we have more 352 // information about the current voicemail messages. 353 // (For example, the current voicemail system can't tell 354 // us the caller-id or timestamp of a message, or tell us the 355 // message count.) 356 357 // But for now, the UI is ultra-simple: if the MWI indication 358 // is supposed to be visible, just show a single generic 359 // notification. 360 361 String notificationTitle = mContext.getString(R.string.notification_voicemail_title); 362 String vmNumber = phone.getVoiceMailNumber(); 363 if (DBG) log("- got vm number: '" + vmNumber + "'"); 364 365 // The voicemail number may be null because: 366 // (1) This phone has no voicemail number. 367 // (2) This phone has a voicemail number, but the SIM isn't ready yet. This may 368 // happen when the device first boots if we get a MWI notification when we 369 // register on the network before the SIM has loaded. In this case, the 370 // SubscriptionListener in CallNotifier will update this once the SIM is loaded. 371 if ((vmNumber == null) && !phone.getIccRecordsLoaded()) { 372 Log.i(LOG_TAG, "updateMwi - Null vm number: SIM records not loaded (yet)..."); 373 return; 374 } 375 376 // Pay attention here; vmCount is an Integer, not an int. This is because: 377 // vmCount == null - means there are voicemail messages waiting. 378 // vmCount == 0 - means there are no voicemail messages waiting. 379 // vmCount > 0 - means there are a specific number of voicemail messages waiting. 380 // Awesome. 381 Integer vmCount = null; 382 383 // TODO: This should be revisited; in the IMS case, the network tells us a count, so 384 // it is strange to stash it and then retrieve it here instead of just passing it. 385 if (TelephonyCapabilities.supportsVoiceMessageCount(phone)) { 386 vmCount = phone.getVoiceMessageCount(); 387 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count); 388 notificationTitle = String.format(titleFormat, vmCount); 389 } 390 391 // This pathway only applies to PSTN accounts; only SIMS have subscription ids. 392 PhoneAccountHandle phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(phone); 393 394 Intent intent; 395 String notificationText; 396 boolean isSettingsIntent = TextUtils.isEmpty(vmNumber); 397 398 if (isSettingsIntent) { 399 // If the voicemail number if unknown, instead of calling voicemail, take the user 400 // to the voicemail settings. 401 notificationText = mContext.getString( 402 R.string.notification_voicemail_no_vm_number); 403 intent = new Intent(VoicemailSettingsActivity.ACTION_ADD_VOICEMAIL); 404 intent.putExtra(SubscriptionInfoHelper.SUB_ID_EXTRA, subId); 405 intent.setClass(mContext, VoicemailSettingsActivity.class); 406 } else { 407 if (mTelephonyManager.getPhoneCount() > 1) { 408 notificationText = subInfo.getDisplayName().toString(); 409 } else { 410 notificationText = String.format( 411 mContext.getString(R.string.notification_voicemail_text_format), 412 PhoneNumberUtils.formatNumber(vmNumber)); 413 } 414 intent = new Intent( 415 Intent.ACTION_CALL, Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", 416 null)); 417 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); 418 } 419 PendingIntent pendingIntent; 420 UserHandle subAssociatedUserHandle = 421 mSubscriptionManager.getSubscriptionUserHandle(subId); 422 if (subAssociatedUserHandle == null) { 423 pendingIntent = PendingIntent.getActivity(mContext, subId /* requestCode */, intent, 424 PendingIntent.FLAG_IMMUTABLE); 425 } else { 426 pendingIntent = PendingIntent.getActivityAsUser(mContext, subId /* requestCode */, 427 intent, PendingIntent.FLAG_IMMUTABLE, null, subAssociatedUserHandle); 428 } 429 430 Resources res = mContext.getResources(); 431 PersistableBundle carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId( 432 subId); 433 Notification.Builder builder = new Notification.Builder(mContext); 434 builder.setSmallIcon(resId) 435 .setWhen(System.currentTimeMillis()) 436 .setColor(subInfo.getIconTint()) 437 .setContentTitle(notificationTitle) 438 .setContentText(notificationText) 439 .setContentIntent(pendingIntent) 440 .setColor(res.getColor(R.color.dialer_theme_color)) 441 .setOngoing(carrierConfig.getBoolean( 442 CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL)) 443 .setChannelId(NotificationChannelController.CHANNEL_ID_VOICE_MAIL) 444 .setOnlyAlertOnce(isRefresh); 445 446 final Notification notification = builder.build(); 447 List<UserHandle> users = getUsersExcludeDying(); 448 for (UserHandle userHandle : users) { 449 boolean isProfile = mUserManager.isProfile(userHandle.getIdentifier()); 450 if (!hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, userHandle) 451 && (userHandle.equals(subAssociatedUserHandle) 452 || (subAssociatedUserHandle == null && !isProfile)) 453 && !maybeSendVoicemailNotificationUsingDefaultDialer(phone, vmCount, 454 vmNumber, pendingIntent, isSettingsIntent, userHandle, isRefresh)) { 455 Log.i(LOG_TAG, "updateMwi: notify userHandle=" + userHandle); 456 notifyAsUser( 457 Integer.toString(subId) /* tag */, 458 VOICEMAIL_NOTIFICATION, 459 notification, 460 userHandle); 461 } 462 } 463 } else { 464 UserHandle subAssociatedUserHandle = 465 mSubscriptionManager.getSubscriptionUserHandle(subId); 466 List<UserHandle> users = getUsersExcludeDying(); 467 for (UserHandle userHandle : users) { 468 boolean isProfile = mUserManager.isProfile(userHandle.getIdentifier()); 469 if (!hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, userHandle) 470 && (userHandle.equals(subAssociatedUserHandle) 471 || (subAssociatedUserHandle == null && !isProfile)) 472 && !maybeSendVoicemailNotificationUsingDefaultDialer(phone, 0, null, null, 473 false, userHandle, isRefresh)) { 474 Log.i(LOG_TAG, "notifyMwi: cancel userHandle=" + userHandle); 475 cancelAsUser( 476 Integer.toString(subId) /* tag */, 477 VOICEMAIL_NOTIFICATION, 478 userHandle); 479 } 480 } 481 } 482 } 483 getUsersExcludeDying()484 private List<UserHandle> getUsersExcludeDying() { 485 long[] serialNumbersOfUsers = 486 mUserManager.getSerialNumbersOfUsers(/* excludeDying= */ true); 487 List<UserHandle> users = new ArrayList<>(serialNumbersOfUsers.length); 488 for (long serialNumber : serialNumbersOfUsers) { 489 UserHandle userHandle = mUserManager.getUserForSerialNumber(serialNumber); 490 if (userHandle != null) { 491 users.add(userHandle); 492 } 493 } 494 return users; 495 } 496 hasUserRestriction(String restrictionKey, UserHandle userHandle)497 private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) { 498 final List<UserManager.EnforcingUser> sources = mUserManager 499 .getUserRestrictionSources(restrictionKey, userHandle); 500 return (sources != null && !sources.isEmpty()); 501 } 502 503 /** 504 * Sends a broadcast with the voicemail notification information to the default dialer. This 505 * method is also used to indicate to the default dialer when to clear the 506 * notification. A pending intent can be passed to the default dialer to indicate an action to 507 * be taken as it would by a notification produced in this class. 508 * @param phone The phone the notification is sent from 509 * @param count The number of pending voicemail messages to indicate on the notification. A 510 * Value of 0 is passed here to indicate that the notification should be cleared. 511 * @param number The voicemail phone number if specified. 512 * @param pendingIntent The intent that should be passed as the action to be taken. 513 * @param isSettingsIntent {@code true} to indicate the pending intent is to launch settings. 514 * otherwise, {@code false} to indicate the intent launches voicemail. 515 * @param userHandle The user to receive the notification. Each user can have their own default 516 * dialer. 517 * @return {@code true} if the default was notified of the notification. 518 */ maybeSendVoicemailNotificationUsingDefaultDialer(Phone phone, Integer count, String number, PendingIntent pendingIntent, boolean isSettingsIntent, UserHandle userHandle, boolean isRefresh)519 private boolean maybeSendVoicemailNotificationUsingDefaultDialer(Phone phone, Integer count, 520 String number, PendingIntent pendingIntent, boolean isSettingsIntent, 521 UserHandle userHandle, boolean isRefresh) { 522 523 if (shouldManageNotificationThroughDefaultDialer(userHandle)) { 524 int subId = phone.getSubId(); 525 // We want to determine if the count of voicemails that we notified to the dialer app 526 // has changed or not. mLastMwiCountSent will initially contain no entry for a subId 527 // meaning no count was ever sent to dialer. The previous count is an Integer (not int) 528 // because the caller of maybeSendVoicemailNotificationUsingDefaultDialer will either 529 // send an instance of Integer with an actual number or "null" if the count isn't known. 530 // See the docs on updateMwi to get more flavor on this lovely logic. 531 // The end result here is we want to know if the "count" we last sent to the dialer for 532 // a sub has changed or not; this will play into whether we want to actually send the 533 // broadcast or not. 534 boolean wasCountSentYet = mLastMwiCountSent.containsKey(subId); 535 Integer previousCount = wasCountSentYet ? mLastMwiCountSent.get(subId) : null; 536 boolean didCountChange = !wasCountSentYet || !Objects.equals(previousCount, count); 537 mLastMwiCountSent.put(subId, count); 538 539 Log.i(LOG_TAG, 540 "maybeSendVoicemailNotificationUsingDefaultDialer: count: " + (wasCountSentYet 541 ? previousCount : "undef") + "->" + count + " (changed=" 542 + didCountChange + ")"); 543 544 Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle); 545 546 /** 547 * isRefresh == true means that we're rebroadcasting because of an 548 * onSubscriptionsChanged callback -- that happens a LOT at boot up. isRefresh == false 549 * happens when TelephonyCallback#onMessageWaitingIndicatorChanged is triggered in 550 * CallNotifier. It's important to note that that count may either be an actual number, 551 * or "we don't know" because the modem doesn't know the actual count. Hence anytime 552 * TelephonyCallback#onMessageWaitingIndicatorChanged occurs, we have to sent the 553 * broadcast even if the count didn't actually change. 554 */ 555 if (!didCountChange && isRefresh) { 556 Log.i(LOG_TAG, "maybeSendVoicemailNotificationUsingDefaultDialer: skip bcast to:" 557 + intent.getPackage() + ", user:" + userHandle); 558 // It's "technically" being sent through the dialer, but we just skipped that so 559 // still return true so we don't post a notification. 560 return true; 561 } 562 563 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 564 intent.setAction(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION); 565 intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, 566 PhoneUtils.makePstnPhoneAccountHandle(phone)); 567 intent.putExtra(TelephonyManager.EXTRA_IS_REFRESH, isRefresh); 568 if (count != null) { 569 intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, count); 570 } 571 572 // Additional information about the voicemail notification beyond the count is only 573 // present when the count not specified or greater than 0. The value of 0 represents 574 // clearing the notification, which does not require additional information. 575 if (count == null || count > 0) { 576 if (!TextUtils.isEmpty(number)) { 577 intent.putExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER, number); 578 } 579 580 if (pendingIntent != null) { 581 intent.putExtra(isSettingsIntent 582 ? TelephonyManager.EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT 583 : TelephonyManager.EXTRA_CALL_VOICEMAIL_INTENT, 584 pendingIntent); 585 } 586 } 587 588 BroadcastOptions bopts = BroadcastOptions.makeBasic(); 589 bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS); 590 591 Log.i(LOG_TAG, "maybeSendVoicemailNotificationUsingDefaultDialer: send via Dialer:" 592 + intent.getPackage() + ", user:" + userHandle); 593 mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, 594 bopts.toBundle()); 595 return true; 596 } 597 Log.i(LOG_TAG, "maybeSendVoicemailNotificationUsingDefaultDialer: not using dialer ; user:" 598 + userHandle); 599 600 return false; 601 } 602 getShowVoicemailIntentForDefaultDialer(UserHandle userHandle)603 private Intent getShowVoicemailIntentForDefaultDialer(UserHandle userHandle) { 604 String dialerPackage = mContext.getSystemService(TelecomManager.class) 605 .getDefaultDialerPackage(userHandle); 606 return new Intent(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION) 607 .setPackage(dialerPackage); 608 } 609 shouldManageNotificationThroughDefaultDialer(UserHandle userHandle)610 private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) { 611 Intent intent = getShowVoicemailIntentForDefaultDialer(userHandle); 612 if (intent == null) { 613 return false; 614 } 615 616 List<ResolveInfo> receivers; 617 if (mFeatureFlags.hsumPackageManager()) { 618 receivers = mContext.createContextAsUser(userHandle, 0) 619 .getPackageManager().queryBroadcastReceivers(intent, 0); 620 } else { 621 receivers = mContext.getPackageManager() 622 .queryBroadcastReceivers(intent, 0); 623 } 624 return receivers.size() > 0; 625 } 626 627 /** 628 * Updates the message call forwarding indicator notification. 629 * 630 * @param visible true if call forwarding enabled 631 */ 632 updateCfi(int subId, boolean visible)633 /* package */ void updateCfi(int subId, boolean visible) { 634 updateCfi(subId, visible, false /* isRefresh */); 635 } 636 637 /** 638 * Updates the message call forwarding indicator notification. 639 * 640 * @param visible true if call forwarding enabled 641 */ updateCfi(int subId, boolean visible, boolean isRefresh)642 /* package */ void updateCfi(int subId, boolean visible, boolean isRefresh) { 643 logi("updateCfi: subId= " + subId + ", visible=" + (visible ? "Y" : "N")); 644 if (visible) { 645 // If Unconditional Call Forwarding (forward all calls) for VOICE 646 // is enabled, just show a notification. We'll default to expanded 647 // view for now, so the there is less confusion about the icon. If 648 // it is deemed too weird to have CF indications as expanded views, 649 // then we'll flip the flag back. 650 651 // TODO: We may want to take a look to see if the notification can 652 // display the target to forward calls to. This will require some 653 // effort though, since there are multiple layers of messages that 654 // will need to propagate that information. 655 656 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId); 657 if (subInfo == null) { 658 Log.w(LOG_TAG, "Found null subscription info for: " + subId); 659 return; 660 } 661 662 String notificationTitle; 663 int resId = R.drawable.stat_sys_phone_call_forward; 664 if (mTelephonyManager.getPhoneCount() > 1) { 665 int slotId = SubscriptionManager.getSlotIndex(subId); 666 resId = (slotId == 0) ? R.drawable.stat_sys_phone_call_forward_sub1 667 : R.drawable.stat_sys_phone_call_forward_sub2; 668 if (subInfo.getDisplayName() != null) { 669 notificationTitle = subInfo.getDisplayName().toString(); 670 } else { 671 notificationTitle = mContext.getString(R.string.labelCF); 672 } 673 } else { 674 notificationTitle = mContext.getString(R.string.labelCF); 675 } 676 677 Notification.Builder builder = new Notification.Builder(mContext) 678 .setSmallIcon(resId) 679 .setColor(subInfo.getIconTint()) 680 .setContentTitle(notificationTitle) 681 .setContentText(mContext.getString(R.string.sum_cfu_enabled_indicator)) 682 .setShowWhen(false) 683 .setOngoing(true) 684 .setChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD) 685 .setOnlyAlertOnce(isRefresh); 686 687 Intent intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS); 688 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 689 SubscriptionInfoHelper.addExtrasToIntent( 690 intent, mSubscriptionManager.getActiveSubscriptionInfo(subId)); 691 builder.setContentIntent(PendingIntent.getActivity(mContext, subId /* requestCode */, 692 intent, PendingIntent.FLAG_IMMUTABLE)); 693 notifyAsUser( 694 Integer.toString(subId) /* tag */, 695 CALL_FORWARD_NOTIFICATION, 696 builder.build(), 697 UserHandle.ALL); 698 } else { 699 List<UserHandle> users = getUsersExcludeDying(); 700 for (UserHandle user : users) { 701 if (mUserManager.isManagedProfile(user.getIdentifier())) { 702 continue; 703 } 704 cancelAsUser( 705 Integer.toString(subId) /* tag */, 706 CALL_FORWARD_NOTIFICATION, 707 user); 708 } 709 } 710 } 711 712 /** 713 * Shows either: 714 * 1) the "Data roaming is on" notification, which 715 * appears when you're roaming and you have the "data roaming" feature turned on for the 716 * given {@code subId}. 717 * or 718 * 2) the "data disconnected due to roaming" notification, which 719 * appears when you lose data connectivity because you're roaming and 720 * you have the "data roaming" feature turned off for the given {@code subId}. 721 * @param subId which subscription it's notifying about. 722 * @param roamingOn whether currently roaming is on or off. If true, we show notification 723 * 1) above; else we show notification 2). 724 */ showDataRoamingNotification(int subId, boolean roamingOn)725 /* package */ void showDataRoamingNotification(int subId, boolean roamingOn) { 726 if (DBG) { 727 log("showDataRoamingNotification() roaming " + (roamingOn ? "on" : "off") 728 + " on subId " + subId); 729 } 730 731 // "Mobile network settings" screen / dialog 732 Intent intent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS); 733 intent.putExtra(Settings.EXTRA_SUB_ID, subId); 734 PendingIntent contentIntent = PendingIntent.getActivity( 735 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE); 736 737 CharSequence contentTitle = mContext.getText(roamingOn 738 ? R.string.roaming_on_notification_title 739 : R.string.roaming_notification_title); 740 CharSequence contentText = mContext.getText(roamingOn 741 ? R.string.roaming_enabled_message 742 : R.string.roaming_reenable_message); 743 744 final Notification.Builder builder = new Notification.Builder(mContext) 745 .setSmallIcon(android.R.drawable.stat_sys_warning) 746 .setContentTitle(contentTitle) 747 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) 748 .setContentText(contentText) 749 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) 750 .setContentIntent(contentIntent); 751 final Notification notif = 752 new Notification.BigTextStyle(builder).bigText(contentText).build(); 753 notifyAsUser(null /* tag */, DATA_ROAMING_NOTIFICATION, notif, UserHandle.ALL); 754 } 755 notifyAsUser(String tag, int id, Notification notification, UserHandle user)756 private void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { 757 try { 758 Context contextForUser = 759 mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); 760 NotificationManager notificationManager = 761 (NotificationManager) contextForUser.getSystemService( 762 Context.NOTIFICATION_SERVICE); 763 notificationManager.notify(tag, id, notification); 764 } catch (PackageManager.NameNotFoundException e) { 765 Log.e(LOG_TAG, "unable to notify for user " + user); 766 e.printStackTrace(); 767 } 768 } 769 cancelAsUser(String tag, int id, UserHandle user)770 private void cancelAsUser(String tag, int id, UserHandle user) { 771 try { 772 Context contextForUser = 773 mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); 774 NotificationManager notificationManager = 775 (NotificationManager) contextForUser.getSystemService( 776 Context.NOTIFICATION_SERVICE); 777 notificationManager.cancel(tag, id); 778 } catch (PackageManager.NameNotFoundException e) { 779 Log.e(LOG_TAG, "unable to cancel for user " + user); 780 e.printStackTrace(); 781 } 782 } 783 784 /** 785 * Turns off the "data disconnected due to roaming" or "Data roaming is on" notification. 786 */ hideDataRoamingNotification()787 /* package */ void hideDataRoamingNotification() { 788 if (DBG) log("hideDataRoamingNotification()..."); 789 cancelAsUser(null, DATA_ROAMING_NOTIFICATION, UserHandle.ALL); 790 } 791 792 /** 793 * Shows the "Limited SIM functionality" warning notification, which appears when using a 794 * special carrier under dual sim. limited function applies for DSDS in general when two SIM 795 * cards share a single radio, thus the voice & data maybe impaired under certain scenarios. 796 */ showLimitedSimFunctionWarningNotification(int subId, @Nullable String carrierName)797 public void showLimitedSimFunctionWarningNotification(int subId, @Nullable String carrierName) { 798 if (DBG) log("showLimitedSimFunctionWarningNotification carrier: " + carrierName 799 + " subId: " + subId); 800 if (mLimitedSimFunctionNotify.contains(subId)) { 801 // handle the case that user swipe the notification but condition triggers 802 // frequently which cause the same notification consistently displayed. 803 if (DBG) log("showLimitedSimFunctionWarningNotification, " 804 + "not display again if already displayed"); 805 return; 806 } 807 // Navigate to "Network Selection Settings" which list all subscriptions. 808 PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, 809 new Intent(ACTION_MOBILE_NETWORK_LIST), PendingIntent.FLAG_IMMUTABLE); 810 // Display phone number from the other sub 811 String line1Num = null; 812 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService( 813 Context.TELEPHONY_SUBSCRIPTION_SERVICE); 814 List<SubscriptionInfo> subList = subMgr.getActiveSubscriptionInfoList(false); 815 for (SubscriptionInfo sub : subList) { 816 if (sub.getSubscriptionId() != subId) { 817 line1Num = mTelephonyManager.getLine1Number(sub.getSubscriptionId()); 818 } 819 } 820 final CharSequence contentText = TextUtils.isEmpty(line1Num) ? 821 String.format(mContext.getText( 822 R.string.limited_sim_function_notification_message).toString(), carrierName) : 823 String.format(mContext.getText( 824 R.string.limited_sim_function_with_phone_num_notification_message).toString(), 825 carrierName, line1Num); 826 final Notification.Builder builder = new Notification.Builder(mContext) 827 .setSmallIcon(R.drawable.ic_sim_card) 828 .setContentTitle(mContext.getText( 829 R.string.limited_sim_function_notification_title)) 830 .setContentText(contentText) 831 .setOnlyAlertOnce(true) 832 .setOngoing(true) 833 .setChannelId(NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY) 834 .setContentIntent(contentIntent); 835 final Notification notification = new Notification.BigTextStyle(builder).bigText( 836 contentText).build(); 837 838 notifyAsUser(Integer.toString(subId), 839 LIMITED_SIM_FUNCTION_NOTIFICATION, 840 notification, UserHandle.ALL); 841 mLimitedSimFunctionNotify.add(subId); 842 } 843 844 /** 845 * Dismiss the "Limited SIM functionality" warning notification for the given subId. 846 */ dismissLimitedSimFunctionWarningNotification(int subId)847 public void dismissLimitedSimFunctionWarningNotification(int subId) { 848 if (DBG) log("dismissLimitedSimFunctionWarningNotification subId: " + subId); 849 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 850 // dismiss all notifications 851 for (int id : mLimitedSimFunctionNotify) { 852 cancelAsUser(Integer.toString(id), 853 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL); 854 } 855 mLimitedSimFunctionNotify.clear(); 856 } else if (mLimitedSimFunctionNotify.contains(subId)) { 857 cancelAsUser(Integer.toString(subId), 858 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL); 859 mLimitedSimFunctionNotify.remove(subId); 860 } 861 } 862 863 /** 864 * Dismiss the "Limited SIM functionality" warning notification for all inactive subscriptions. 865 */ dismissLimitedSimFunctionWarningNotificationForInactiveSubs()866 public void dismissLimitedSimFunctionWarningNotificationForInactiveSubs() { 867 if (DBG) log("dismissLimitedSimFunctionWarningNotificationForInactiveSubs"); 868 // dismiss notification for inactive subscriptions. 869 // handle the corner case that SIM change by SIM refresh doesn't clear the notification 870 // from the old SIM if both old & new SIM configured to display the notification. 871 mLimitedSimFunctionNotify.removeIf(id -> { 872 if (!mSubscriptionManager.isActiveSubId(id)) { 873 cancelAsUser(Integer.toString(id), 874 LIMITED_SIM_FUNCTION_NOTIFICATION, UserHandle.ALL); 875 return true; 876 } 877 return false; 878 }); 879 } 880 881 /** 882 * Display the network selection "no service" notification 883 * @param operator is the numeric operator number 884 * @param subId is the subscription ID 885 */ showNetworkSelection(String operator, int subId)886 private void showNetworkSelection(String operator, int subId) { 887 if (DBG) log("showNetworkSelection(" + operator + ")..."); 888 889 if (!TextUtils.isEmpty(operator)) { 890 operator = String.format(" (%s)", operator); 891 } 892 Notification.Builder builder = new Notification.Builder(mContext) 893 .setSmallIcon(android.R.drawable.stat_sys_warning) 894 .setContentTitle(mContext.getString(R.string.notification_network_selection_title)) 895 .setContentText( 896 mContext.getString(R.string.notification_network_selection_text, operator)) 897 .setShowWhen(false) 898 .setOngoing(true) 899 .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT); 900 901 // create the target network operators settings intent 902 Intent intent = new Intent(Intent.ACTION_MAIN); 903 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 904 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 905 // Use MobileNetworkSettings to handle the selection intent 906 intent.setComponent(new ComponentName( 907 mContext.getString(R.string.mobile_network_settings_package), 908 mContext.getString(R.string.mobile_network_settings_class))); 909 intent.putExtra(Settings.EXTRA_SUB_ID, subId); 910 builder.setContentIntent( 911 PendingIntent.getActivity(mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE)); 912 notifyAsUser( 913 Integer.toString(subId) /* tag */, 914 SELECTED_OPERATOR_FAIL_NOTIFICATION, 915 builder.build(), 916 UserHandle.ALL); 917 mSelectedUnavailableNotify.put(subId, true); 918 } 919 920 /** 921 * Turn off the network selection "no service" notification 922 */ cancelNetworkSelection(int subId)923 private void cancelNetworkSelection(int subId) { 924 if (DBG) log("cancelNetworkSelection()..."); 925 cancelAsUser( 926 Integer.toString(subId) /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION, 927 UserHandle.ALL); 928 } 929 930 /** 931 * Update notification about no service of user selected operator 932 * 933 * @param serviceState Phone service state 934 * @param subId The subscription ID 935 */ updateNetworkSelection(int serviceState, int subId)936 void updateNetworkSelection(int serviceState, int subId) { 937 // for dismissNetworkSelectionNotificationOnSimDisable feature enabled. 938 int phoneId = SubscriptionManager.getPhoneId(subId); 939 Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ? 940 PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone(); 941 if (TelephonyCapabilities.supportsNetworkSelection(phone)) { 942 if (SubscriptionManager.isValidSubscriptionId(subId) 943 && mSubscriptionManager.isActiveSubId(subId)) { 944 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 945 String selectedNetworkOperatorName = 946 sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, ""); 947 // get the shared preference of network_selection. 948 // empty is auto mode, otherwise it is the operator alpha name 949 // in case there is no operator name, check the operator numeric 950 if (TextUtils.isEmpty(selectedNetworkOperatorName)) { 951 selectedNetworkOperatorName = 952 sp.getString(Phone.NETWORK_SELECTION_KEY + subId, ""); 953 } 954 boolean isManualSelection; 955 // if restoring manual selection is controlled by framework, then get network 956 // selection from shared preference, otherwise get from real network indicators. 957 boolean restoreSelection = !mContext.getResources().getBoolean( 958 com.android.internal.R.bool.skip_restoring_network_selection); 959 if (restoreSelection) { 960 isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName); 961 } else { 962 isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection(); 963 } 964 965 if (DBG) { 966 log("updateNetworkSelection()..." + "state = " + serviceState + " new network " 967 + (isManualSelection ? selectedNetworkOperatorName : "")); 968 } 969 970 if (isManualSelection 971 && isSubscriptionVisibleToUser( 972 mSubscriptionManager.getActiveSubscriptionInfo(subId))) { 973 mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName); 974 shouldShowNotification(serviceState, subId); 975 } else { 976 dismissNetworkSelectionNotification(subId); 977 clearUpNetworkSelectionNotificationParam(subId); 978 } 979 } else { 980 if (DBG) { 981 log("updateNetworkSelection()... state = " + serviceState 982 + " not updating network due to invalid subId " + subId); 983 } 984 dismissNetworkSelectionNotificationForInactiveSubId(); 985 } 986 } 987 } 988 989 /** 990 * Update notification about no service of user selected operator. 991 * For dismissNetworkSelectionNotificationOnSimDisable feature disabled. 992 * 993 * @param serviceState Phone service state 994 * @param subId The subscription ID 995 */ updateNetworkSelectionForFeatureDisabled(int serviceState, int subId)996 private void updateNetworkSelectionForFeatureDisabled(int serviceState, int subId) { 997 int phoneId = SubscriptionManager.getPhoneId(subId); 998 Phone phone = SubscriptionManager.isValidPhoneId(phoneId) 999 ? PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone(); 1000 if (TelephonyCapabilities.supportsNetworkSelection(phone)) { 1001 if (SubscriptionManager.isValidSubscriptionId(subId)) { 1002 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 1003 String selectedNetworkOperatorName = 1004 sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, ""); 1005 // get the shared preference of network_selection. 1006 // empty is auto mode, otherwise it is the operator alpha name 1007 // in case there is no operator name, check the operator numeric 1008 if (TextUtils.isEmpty(selectedNetworkOperatorName)) { 1009 selectedNetworkOperatorName = 1010 sp.getString(Phone.NETWORK_SELECTION_KEY + subId, ""); 1011 } 1012 boolean isManualSelection; 1013 // if restoring manual selection is controlled by framework, then get network 1014 // selection from shared preference, otherwise get from real network indicators. 1015 boolean restoreSelection = !mContext.getResources().getBoolean( 1016 com.android.internal.R.bool.skip_restoring_network_selection); 1017 if (restoreSelection) { 1018 isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName); 1019 } else { 1020 isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection(); 1021 } 1022 1023 if (DBG) { 1024 log("updateNetworkSelection()..." + "state = " + serviceState + " new network " 1025 + (isManualSelection ? selectedNetworkOperatorName : "")); 1026 } 1027 1028 if (isManualSelection 1029 && isSubscriptionVisibleToUser( 1030 mSubscriptionManager.getActiveSubscriptionInfo(subId))) { 1031 mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName); 1032 shouldShowNotification(serviceState, subId); 1033 } else { 1034 dismissNetworkSelectionNotification(subId); 1035 clearUpNetworkSelectionNotificationParam(subId); 1036 } 1037 } else { 1038 if (DBG) log("updateNetworkSelection()..." + "state = " + 1039 serviceState + " not updating network due to invalid subId " + subId); 1040 dismissNetworkSelectionNotificationForInactiveSubId(); 1041 } 1042 } 1043 } 1044 1045 // TODO(b/261916533) This should be handled by SubscriptionManager#isSubscriptionVisible(), 1046 // but that method doesn't support system callers, so here we are. isSubscriptionVisibleToUser(SubscriptionInfo subInfo)1047 private boolean isSubscriptionVisibleToUser(SubscriptionInfo subInfo) { 1048 return subInfo != null && (!subInfo.isOpportunistic() || subInfo.getGroupUuid() == null); 1049 } 1050 dismissNetworkSelectionNotification(int subId)1051 private void dismissNetworkSelectionNotification(int subId) { 1052 if (mSelectedUnavailableNotify.get(subId, false)) { 1053 cancelNetworkSelection(subId); 1054 mSelectedUnavailableNotify.remove(subId); 1055 } 1056 } 1057 1058 /** 1059 * Dismiss the network selection "no service" notification for all inactive subscriptions. 1060 */ dismissNetworkSelectionNotificationForInactiveSubId()1061 public void dismissNetworkSelectionNotificationForInactiveSubId() { 1062 for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) { 1063 int subId = mSelectedUnavailableNotify.keyAt(i); 1064 if (!mSubscriptionManager.isActiveSubId(subId)) { 1065 dismissNetworkSelectionNotification(subId); 1066 clearUpNetworkSelectionNotificationParam(subId); 1067 } 1068 } 1069 } 1070 log(String msg)1071 private void log(String msg) { 1072 Log.d(LOG_TAG, msg); 1073 } 1074 logi(String msg)1075 private void logi(String msg) { 1076 Log.i(LOG_TAG, msg); 1077 } 1078 shouldShowNotification(int serviceState, int subId)1079 private void shouldShowNotification(int serviceState, int subId) { 1080 // "Network selection unavailable" notification should only show when network selection is 1081 // visible to the end user. Some CC items e.g. KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL 1082 // can be overridden to hide the network selection to the end user. In this case, the 1083 // notification is not shown to avoid confusion to the end user. 1084 if (!shouldDisplayNetworkSelectOptions(subId)) { 1085 logi("Carrier configs refuse to show network selection not available notification"); 1086 return; 1087 } 1088 1089 // In case network selection notification shows up repeatedly under 1090 // unstable network condition. The logic is to check whether or not 1091 // the service state keeps in no service condition for at least 1092 // {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}. 1093 // And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times. 1094 // To avoid the notification showing up for the momentary state. 1095 if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) { 1096 if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE) 1097 != ServiceState.STATE_OUT_OF_SERVICE) { 1098 mOOSTimestamp.put(subId, getTimeStamp()); 1099 } 1100 if ((getTimeStamp() - mOOSTimestamp.get(subId, 0L) 1101 >= NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS) 1102 || mPendingEventCounter.get(subId, 0) 1103 > NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES) { 1104 showNetworkSelection(mSelectedNetworkOperatorName.get(subId), subId); 1105 clearUpNetworkSelectionNotificationParam(subId); 1106 } else { 1107 startPendingNetworkSelectionNotification(subId); 1108 } 1109 } else { 1110 dismissNetworkSelectionNotification(subId); 1111 } 1112 mPreviousServiceState.put(subId, serviceState); 1113 if (DBG) { 1114 log("shouldShowNotification()..." + " subId = " + subId 1115 + " serviceState = " + serviceState 1116 + " mOOSTimestamp = " + mOOSTimestamp 1117 + " mPendingEventCounter = " + mPendingEventCounter); 1118 } 1119 } 1120 1121 // TODO(b/243010310): merge methods below with Settings#MobileNetworkUtils and optimize them. 1122 // The methods below are copied from com.android.settings.network.telephony.MobileNetworkUtils 1123 // to make sure the network selection unavailable notification should not show when Network 1124 // Selection menu is not present in Settings app. shouldDisplayNetworkSelectOptions(int subId)1125 private boolean shouldDisplayNetworkSelectOptions(int subId) { 1126 final TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(subId); 1127 final CarrierConfigManager carrierConfigManager = mContext.getSystemService( 1128 CarrierConfigManager.class); 1129 final PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId); 1130 1131 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID 1132 || carrierConfig == null 1133 || !carrierConfig.getBoolean( 1134 CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL) 1135 || carrierConfig.getBoolean( 1136 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) 1137 || (carrierConfig.getBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL) 1138 && !telephonyManager.isManualNetworkSelectionAllowed())) { 1139 return false; 1140 } 1141 1142 if (isWorldMode(carrierConfig)) { 1143 final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf( 1144 (int) telephonyManager.getAllowedNetworkTypesForReason( 1145 TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)); 1146 if (networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO) { 1147 return false; 1148 } 1149 if (shouldSpeciallyUpdateGsmCdma(telephonyManager, carrierConfig)) { 1150 return false; 1151 } 1152 if (networkMode == RILConstants.NETWORK_MODE_LTE_GSM_WCDMA) { 1153 return true; 1154 } 1155 } 1156 1157 return isGsmBasicOptions(telephonyManager, carrierConfig); 1158 } 1159 isWorldMode(@onNull PersistableBundle carrierConfig)1160 private static boolean isWorldMode(@NonNull PersistableBundle carrierConfig) { 1161 return carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL); 1162 } 1163 shouldSpeciallyUpdateGsmCdma(@onNull TelephonyManager telephonyManager, @NonNull PersistableBundle carrierConfig)1164 private static boolean shouldSpeciallyUpdateGsmCdma(@NonNull TelephonyManager telephonyManager, 1165 @NonNull PersistableBundle carrierConfig) { 1166 if (!isWorldMode(carrierConfig)) { 1167 return false; 1168 } 1169 1170 final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf( 1171 (int) telephonyManager.getAllowedNetworkTypesForReason( 1172 TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)); 1173 if (networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM 1174 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA 1175 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA 1176 || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA 1177 || networkMode 1178 == RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA 1179 || networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) { 1180 if (!isTdscdmaSupported(telephonyManager, carrierConfig)) { 1181 return true; 1182 } 1183 } 1184 1185 return false; 1186 } 1187 isTdscdmaSupported(@onNull TelephonyManager telephonyManager, @NonNull PersistableBundle carrierConfig)1188 private static boolean isTdscdmaSupported(@NonNull TelephonyManager telephonyManager, 1189 @NonNull PersistableBundle carrierConfig) { 1190 if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL)) { 1191 return true; 1192 } 1193 final String[] numericArray = carrierConfig.getStringArray( 1194 CarrierConfigManager.KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY); 1195 if (numericArray == null) { 1196 return false; 1197 } 1198 final ServiceState serviceState = telephonyManager.getServiceState(); 1199 final String operatorNumeric = 1200 (serviceState != null) ? serviceState.getOperatorNumeric() : null; 1201 if (operatorNumeric == null) { 1202 return false; 1203 } 1204 for (String numeric : numericArray) { 1205 if (operatorNumeric.equals(numeric)) { 1206 return true; 1207 } 1208 } 1209 return false; 1210 } 1211 isGsmBasicOptions(@onNull TelephonyManager telephonyManager, @NonNull PersistableBundle carrierConfig)1212 private static boolean isGsmBasicOptions(@NonNull TelephonyManager telephonyManager, 1213 @NonNull PersistableBundle carrierConfig) { 1214 if (!carrierConfig.getBoolean( 1215 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) 1216 && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { 1217 return true; 1218 } 1219 1220 if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { 1221 return true; 1222 } 1223 1224 return false; 1225 } 1226 // END of TODO:(b/243010310): merge methods above with Settings#MobileNetworkUtils and optimize. 1227 startPendingNetworkSelectionNotification(int subId)1228 private void startPendingNetworkSelectionNotification(int subId) { 1229 if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) { 1230 if (DBG) { 1231 log("startPendingNetworkSelectionNotification: subId = " + subId); 1232 } 1233 mHandler.sendMessageDelayed( 1234 mHandler.obtainMessage(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId), 1235 NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS); 1236 mPendingEventCounter.put(subId, mPendingEventCounter.get(subId, 0) + 1); 1237 } 1238 } 1239 clearUpNetworkSelectionNotificationParam(int subId)1240 private void clearUpNetworkSelectionNotificationParam(int subId) { 1241 if (mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) { 1242 mHandler.removeMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId); 1243 } 1244 mPreviousServiceState.remove(subId); 1245 mOOSTimestamp.remove(subId); 1246 mPendingEventCounter.remove(subId); 1247 mSelectedNetworkOperatorName.remove(subId); 1248 } 1249 1250 @VisibleForTesting getTimeStamp()1251 public long getTimeStamp() { 1252 return SystemClock.elapsedRealtime(); 1253 } 1254 } 1255