1 /* 2 * Copyright (C) 2023 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.internal.telephony.data; 18 19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 20 import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX; 21 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX; 22 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.AlarmManager; 27 import android.app.Notification; 28 import android.app.NotificationManager; 29 import android.app.PendingIntent; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.net.NetworkCapabilities; 33 import android.os.AsyncResult; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Looper; 37 import android.os.Message; 38 import android.os.SystemClock; 39 import android.provider.Settings; 40 import android.telephony.AccessNetworkConstants; 41 import android.telephony.NetworkRegistrationInfo; 42 import android.telephony.NetworkRegistrationInfo.RegistrationState; 43 import android.telephony.ServiceState; 44 import android.telephony.SignalStrength; 45 import android.telephony.SubscriptionInfo; 46 import android.telephony.TelephonyDisplayInfo; 47 import android.util.IndentingPrintWriter; 48 import android.util.LocalLog; 49 50 import com.android.internal.telephony.Phone; 51 import com.android.internal.telephony.PhoneFactory; 52 import com.android.internal.telephony.flags.FeatureFlags; 53 import com.android.internal.telephony.flags.FeatureFlagsImpl; 54 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 55 import com.android.internal.telephony.subscription.SubscriptionManagerService; 56 import com.android.internal.telephony.util.NotificationChannelController; 57 import com.android.telephony.Rlog; 58 59 60 import java.io.FileDescriptor; 61 import java.io.PrintWriter; 62 import java.lang.annotation.Retention; 63 import java.lang.annotation.RetentionPolicy; 64 import java.util.Arrays; 65 import java.util.HashMap; 66 import java.util.Map; 67 import java.util.Set; 68 import java.util.concurrent.TimeUnit; 69 import java.util.stream.Collectors; 70 71 /** 72 * Recommend a data phone to use based on its availability. 73 */ 74 public class AutoDataSwitchController extends Handler { 75 /** Registration state changed. */ 76 public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1; 77 /** Telephony Display Info changed. */ 78 public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2; 79 /** Signal Strength changed. */ 80 public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3; 81 /** Default network capabilities changed or lost. */ 82 public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4; 83 /** Data enabled settings changed. */ 84 public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5; 85 /** Retry due to previous validation failed. */ 86 public static final int EVALUATION_REASON_RETRY_VALIDATION = 6; 87 /** Sim loaded which means slot mapping became available. */ 88 public static final int EVALUATION_REASON_SIM_LOADED = 7; 89 /** Voice call ended. */ 90 public static final int EVALUATION_REASON_VOICE_CALL_END = 8; 91 @Retention(RetentionPolicy.SOURCE) 92 @IntDef(prefix = "EVALUATION_REASON_", 93 value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED, 94 EVALUATION_REASON_DISPLAY_INFO_CHANGED, 95 EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED, 96 EVALUATION_REASON_DEFAULT_NETWORK_CHANGED, 97 EVALUATION_REASON_DATA_SETTINGS_CHANGED, 98 EVALUATION_REASON_RETRY_VALIDATION, 99 EVALUATION_REASON_SIM_LOADED, 100 EVALUATION_REASON_VOICE_CALL_END}) 101 public @interface AutoDataSwitchEvaluationReason {} 102 103 private static final String LOG_TAG = "ADSC"; 104 105 /** Event for service state changed. */ 106 private static final int EVENT_SERVICE_STATE_CHANGED = 1; 107 /** Event for display info changed. This is for getting 5G NSA or mmwave information. */ 108 private static final int EVENT_DISPLAY_INFO_CHANGED = 2; 109 /** Event for evaluate auto data switch opportunity. */ 110 private static final int EVENT_EVALUATE_AUTO_SWITCH = 3; 111 /** Event for signal strength changed. */ 112 private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4; 113 /** Event indicates the switch state is stable, proceed to validation as the next step. */ 114 private static final int EVENT_STABILITY_CHECK_PASSED = 5; 115 /** Event when subscriptions changed. */ 116 private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6; 117 118 /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */ 119 private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 120 /** 121 * When starting this activity, this extra can also be specified to supply a Bundle of arguments 122 * to pass to that fragment when it is instantiated during the initial creation of the activity. 123 */ 124 private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = 125 ":settings:show_fragment_args"; 126 /** The resource ID of the auto data switch fragment in settings. **/ 127 private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; 128 /** Notification tag **/ 129 private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch"; 130 /** Notification ID **/ 131 private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1; 132 133 /** 134 * The threshold of long timer, longer than or equal to which we use alarm manager to schedule 135 * instead of handler. 136 */ 137 private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit 138 .MINUTES.toMillis(1); 139 140 @NonNull 141 private final LocalLog mLocalLog = new LocalLog(128); 142 @NonNull 143 private final Context mContext; 144 @NonNull 145 private static FeatureFlags sFeatureFlags = new FeatureFlagsImpl(); 146 @NonNull 147 private final SubscriptionManagerService mSubscriptionManagerService; 148 @NonNull 149 private final PhoneSwitcher mPhoneSwitcher; 150 @NonNull 151 private final AutoDataSwitchControllerCallback mPhoneSwitcherCallback; 152 @NonNull 153 private final AlarmManager mAlarmManager; 154 /** A map of a scheduled event to its associated extra for action when the event fires off. */ 155 @NonNull 156 private final Map<Integer, Object> mScheduledEventsToExtras; 157 /** A map of an event to its associated alarm listener callback for when the event fires off. */ 158 @NonNull 159 private final Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener; 160 /** 161 * Event extras for checking environment stability. 162 * @param targetPhoneId The target phone Id to switch to when the stability check pass. 163 * @param isForPerformance Whether the switch is due to RAT/signal strength performance. 164 * @param needValidation Whether ping test needs to pass. 165 */ StabilityEventExtra(int targetPhoneId, boolean isForPerformance, boolean needValidation)166 private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance, 167 boolean needValidation) {} 168 169 /** 170 * Event extras for evaluating switch environment. 171 * @param evaluateReason The reason that triggers the evaluation. 172 */ EvaluateEventExtra(@utoDataSwitchEvaluationReason int evaluateReason)173 private record EvaluateEventExtra(@AutoDataSwitchEvaluationReason int evaluateReason) {} 174 private boolean mDefaultNetworkIsOnNonCellular = false; 175 /** {@code true} if we've displayed the notification the first time auto switch occurs **/ 176 private boolean mDisplayedNotification = false; 177 /** 178 * Configurable time threshold in ms to define an internet connection status to be stable(e.g. 179 * out of service, in service, wifi is the default active network.etc), while -1 indicates auto 180 * switch feature disabled. 181 */ 182 private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1; 183 /** 184 * Configurable time threshold in ms to define an internet connection performance status to be 185 * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates 186 * auto switch feature based on RAT/SS is disabled. 187 */ 188 private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1; 189 /** 190 * The tolerated gap of score for auto data switch decision, larger than which the device will 191 * switch to the SIM with higher score. If 0, the device will always switch to the higher score 192 * SIM. If < 0, the network type and signal strength based auto switch is disabled. 193 */ 194 private int mScoreTolerance = -1; 195 /** 196 * {@code true} if requires ping test before switching preferred data modem; otherwise, switch 197 * even if ping test fails. 198 */ 199 private boolean mRequirePingTestBeforeSwitch = true; 200 /** 201 * TODO: remove after V. 202 * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not 203 * usable(OOS or disabled roaming) 204 */ 205 private boolean mAllowNddsRoaming = true; 206 /** The count of consecutive auto switch validation failure **/ 207 private int mAutoSwitchValidationFailedCount = 0; 208 /** 209 * The maximum number of retries when a validation for switching failed. 210 */ 211 private int mAutoDataSwitchValidationMaxRetry; 212 213 /** The signal status of phones, where index corresponds to phone Id. */ 214 @NonNull 215 private PhoneSignalStatus[] mPhonesSignalStatus; 216 /** 217 * The phone Id of the pending switching phone. Used for pruning frequent switch evaluation. 218 */ 219 private int mSelectedTargetPhoneId = INVALID_PHONE_INDEX; 220 221 /** 222 * To track the signal status of a phone in order to evaluate whether it's a good candidate to 223 * switch to. 224 */ 225 private static class PhoneSignalStatus { 226 /** 227 * How preferred the current phone is. 228 */ 229 enum UsableState { 230 HOME(2), 231 ROAMING_ENABLED(1), 232 NON_TERRESTRIAL(0), 233 NOT_USABLE(-1); 234 /** 235 * The higher the score, the more preferred. 236 * HOME is preferred over ROAMING assuming roaming is metered. 237 */ 238 final int mScore; UsableState(int score)239 UsableState(int score) { 240 this.mScore = score; 241 } 242 } 243 /** The phone */ 244 @NonNull private final Phone mPhone; 245 /** Data registration state of the phone */ 246 @RegistrationState private int mDataRegState; 247 /** Current Telephony display info of the phone */ 248 @NonNull private TelephonyDisplayInfo mDisplayInfo; 249 /** Signal strength of the phone */ 250 @NonNull private SignalStrength mSignalStrength; 251 /** {@code true} if this slot is listening for events. */ 252 private boolean mListeningForEvents; PhoneSignalStatus(@onNull Phone phone)253 private PhoneSignalStatus(@NonNull Phone phone) { 254 this.mPhone = phone; 255 this.mDataRegState = phone.getServiceState().getNetworkRegistrationInfo( 256 NetworkRegistrationInfo.DOMAIN_PS, 257 AccessNetworkConstants.TRANSPORT_TYPE_WWAN) 258 .getRegistrationState(); 259 this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo(); 260 this.mSignalStrength = phone.getSignalStrength(); 261 } 262 263 /** 264 * @return the current score of this phone. 0 indicates out of service and it will never be 265 * selected as the secondary data candidate. 266 */ getRatSignalScore()267 private int getRatSignalScore() { 268 return isInService(mDataRegState) 269 ? mPhone.getDataNetworkController().getDataConfigManager() 270 .getAutoDataSwitchScore(mDisplayInfo, mSignalStrength) : 0; 271 } 272 273 /** 274 * @return The current usable state of the phone. 275 */ getUsableState()276 private UsableState getUsableState() { 277 ServiceState serviceState = mPhone.getServiceState(); 278 boolean isUsingNonTerrestrialNetwork = sFeatureFlags.carrierEnabledSatelliteFlag() 279 && (serviceState != null) && serviceState.isUsingNonTerrestrialNetwork(); 280 281 return switch (mDataRegState) { 282 case NetworkRegistrationInfo.REGISTRATION_STATE_HOME -> { 283 if (isUsingNonTerrestrialNetwork) { 284 yield UsableState.NON_TERRESTRIAL; 285 } 286 yield UsableState.HOME; 287 } 288 case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING -> { 289 if (mPhone.getDataRoamingEnabled()) { 290 if (isUsingNonTerrestrialNetwork) { 291 yield UsableState.NON_TERRESTRIAL; 292 } 293 yield UsableState.ROAMING_ENABLED; 294 } 295 yield UsableState.NOT_USABLE; 296 } 297 default -> UsableState.NOT_USABLE; 298 }; 299 } 300 301 @Override 302 public String toString() { 303 return "{phone " + mPhone.getPhoneId() 304 + " score=" + getRatSignalScore() + " dataRegState=" 305 + NetworkRegistrationInfo.registrationStateToString(mDataRegState) 306 + " " + getUsableState() + " " + mDisplayInfo 307 + " signalStrength=" + mSignalStrength.getLevel() 308 + " listeningForEvents=" + mListeningForEvents 309 + "}"; 310 311 } 312 } 313 314 /** 315 * This is the callback used for listening events from {@link AutoDataSwitchController}. 316 */ 317 public abstract static class AutoDataSwitchControllerCallback { 318 /** 319 * Called when a target data phone is recommended by the controller. 320 * @param targetPhoneId The target phone Id. 321 * @param needValidation {@code true} if need a ping test to pass before switching. 322 */ 323 public abstract void onRequireValidation(int targetPhoneId, boolean needValidation); 324 325 /** 326 * Called when a target data phone is demanded by the controller. 327 * @param targetPhoneId The target phone Id. 328 * @param reason The reason for the demand. 329 */ 330 public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId, 331 @AutoDataSwitchEvaluationReason int reason); 332 333 /** 334 * Called when the controller asks to cancel any pending validation attempts because the 335 * environment is no longer suited for switching. 336 */ 337 public abstract void onRequireCancelAnyPendingAutoSwitchValidation(); 338 } 339 340 /** 341 * @param context Context. 342 * @param looper Main looper. 343 * @param phoneSwitcher Phone switcher. 344 * @param phoneSwitcherCallback Callback for phone switcher to execute. 345 */ 346 public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper, 347 @NonNull PhoneSwitcher phoneSwitcher, @NonNull FeatureFlags featureFlags, 348 @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) { 349 super(looper); 350 mContext = context; 351 sFeatureFlags = featureFlags; 352 mPhoneSwitcherCallback = phoneSwitcherCallback; 353 mAlarmManager = context.getSystemService(AlarmManager.class); 354 mScheduledEventsToExtras = new HashMap<>(); 355 mEventsToAlarmListener = new HashMap<>(); 356 mSubscriptionManagerService = SubscriptionManagerService.getInstance(); 357 mPhoneSwitcher = phoneSwitcher; 358 readDeviceResourceConfig(); 359 int numActiveModems = PhoneFactory.getPhones().length; 360 mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems]; 361 // Listening on all slots on boot up to make sure nothing missed. Later the tracking is 362 // pruned upon subscriptions changed. 363 for (int phoneId = 0; phoneId < numActiveModems; phoneId++) { 364 registerAllEventsForPhone(phoneId); 365 } 366 } 367 368 /** 369 * Called when active modem count changed, update all tracking events. 370 * @param numActiveModems The current number of active modems. 371 */ 372 public synchronized void onMultiSimConfigChanged(int numActiveModems) { 373 int oldActiveModems = mPhonesSignalStatus.length; 374 if (oldActiveModems == numActiveModems) return; 375 // Dual -> Single 376 for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) { 377 unregisterAllEventsForPhone(phoneId); 378 } 379 mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems); 380 // Signal -> Dual 381 for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) { 382 registerAllEventsForPhone(phoneId); 383 } 384 logl("onMultiSimConfigChanged: " + Arrays.toString(mPhonesSignalStatus)); 385 } 386 387 /** Notify subscriptions changed. */ 388 public void notifySubscriptionsMappingChanged() { 389 sendEmptyMessage(EVENT_SUBSCRIPTIONS_CHANGED); 390 } 391 392 /** 393 * On subscription changed, register/unregister events on phone Id slot that has active/inactive 394 * sub to reduce unnecessary tracking. 395 */ 396 private void onSubscriptionsChanged() { 397 Set<Integer> activePhoneIds = Arrays.stream(mSubscriptionManagerService 398 .getActiveSubIdList(true /*visibleOnly*/)) 399 .map(mSubscriptionManagerService::getPhoneId) 400 .boxed() 401 .collect(Collectors.toSet()); 402 // Track events only if there are at least two active visible subscriptions. 403 if (activePhoneIds.size() < 2) activePhoneIds.clear(); 404 boolean changed = false; 405 for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { 406 if (activePhoneIds.contains(phoneId) 407 && !mPhonesSignalStatus[phoneId].mListeningForEvents) { 408 registerAllEventsForPhone(phoneId); 409 changed = true; 410 } else if (!activePhoneIds.contains(phoneId) 411 && mPhonesSignalStatus[phoneId].mListeningForEvents) { 412 unregisterAllEventsForPhone(phoneId); 413 changed = true; 414 } 415 } 416 if (changed) logl("onSubscriptionChanged: " + Arrays.toString(mPhonesSignalStatus)); 417 } 418 419 /** 420 * Register all tracking events for a phone. 421 * @param phoneId The phone to register for all events. 422 */ 423 private void registerAllEventsForPhone(int phoneId) { 424 Phone phone = PhoneFactory.getPhone(phoneId); 425 if (phone != null && isActiveModemPhone(phoneId)) { 426 mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone); 427 phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged( 428 this, EVENT_DISPLAY_INFO_CHANGED, phoneId); 429 phone.getSignalStrengthController().registerForSignalStrengthChanged( 430 this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId); 431 phone.getServiceStateTracker().registerForServiceStateChanged(this, 432 EVENT_SERVICE_STATE_CHANGED, phoneId); 433 mPhonesSignalStatus[phoneId].mListeningForEvents = true; 434 } else { 435 loge("Unexpected null phone " + phoneId + " when register all events"); 436 } 437 } 438 439 /** 440 * Unregister all tracking events for a phone. 441 * @param phoneId The phone to unregister for all events. 442 */ 443 private void unregisterAllEventsForPhone(int phoneId) { 444 if (isActiveModemPhone(phoneId)) { 445 Phone phone = mPhonesSignalStatus[phoneId].mPhone; 446 phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this); 447 phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this); 448 phone.getServiceStateTracker().unregisterForServiceStateChanged(this); 449 mPhonesSignalStatus[phoneId].mListeningForEvents = false; 450 } else { 451 loge("Unexpected out of bound phone " + phoneId + " when unregister all events"); 452 } 453 } 454 455 /** 456 * Read the default device config from any default phone because the resource config are per 457 * device. No need to register callback for the same reason. 458 */ 459 private void readDeviceResourceConfig() { 460 Phone phone = PhoneFactory.getDefaultPhone(); 461 DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); 462 mScoreTolerance = dataConfig.getAutoDataSwitchScoreTolerance(); 463 mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); 464 mAllowNddsRoaming = dataConfig.doesAutoDataSwitchAllowRoaming(); 465 mAutoDataSwitchAvailabilityStabilityTimeThreshold = 466 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); 467 mAutoDataSwitchPerformanceStabilityTimeThreshold = 468 dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold(); 469 mAutoDataSwitchValidationMaxRetry = 470 dataConfig.getAutoDataSwitchValidationMaxRetry(); 471 } 472 473 @Override 474 public void handleMessage(@NonNull Message msg) { 475 AsyncResult ar; 476 Object obj; 477 int phoneId; 478 switch (msg.what) { 479 case EVENT_SERVICE_STATE_CHANGED: 480 ar = (AsyncResult) msg.obj; 481 phoneId = (int) ar.userObj; 482 onServiceStateChanged(phoneId); 483 break; 484 case EVENT_DISPLAY_INFO_CHANGED: 485 ar = (AsyncResult) msg.obj; 486 phoneId = (int) ar.userObj; 487 onDisplayInfoChanged(phoneId); 488 break; 489 case EVENT_SIGNAL_STRENGTH_CHANGED: 490 ar = (AsyncResult) msg.obj; 491 phoneId = (int) ar.userObj; 492 onSignalStrengthChanged(phoneId); 493 break; 494 case EVENT_EVALUATE_AUTO_SWITCH: 495 obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH); 496 if (obj instanceof EvaluateEventExtra extra) { 497 mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH); 498 onEvaluateAutoDataSwitch(extra.evaluateReason); 499 } 500 break; 501 case EVENT_STABILITY_CHECK_PASSED: 502 obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED); 503 if (obj instanceof StabilityEventExtra extra) { 504 int targetPhoneId = extra.targetPhoneId; 505 boolean needValidation = extra.needValidation; 506 log("require validation on phone " + targetPhoneId 507 + (needValidation ? "" : " no") + " need to pass"); 508 mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED); 509 mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation); 510 } 511 break; 512 case EVENT_SUBSCRIPTIONS_CHANGED: 513 onSubscriptionsChanged(); 514 break; 515 default: 516 loge("Unexpected event " + msg.what); 517 } 518 } 519 520 /** 521 * Called when registration state changed. 522 */ 523 private void onServiceStateChanged(int phoneId) { 524 Phone phone = PhoneFactory.getPhone(phoneId); 525 if (phone != null && isActiveModemPhone(phoneId)) { 526 int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState; 527 int newRegState = phone.getServiceState() 528 .getNetworkRegistrationInfo( 529 NetworkRegistrationInfo.DOMAIN_PS, 530 AccessNetworkConstants.TRANSPORT_TYPE_WWAN) 531 .getRegistrationState(); 532 if (newRegState != oldRegState) { 533 mPhonesSignalStatus[phoneId].mDataRegState = newRegState; 534 if (isInService(oldRegState) != isInService(newRegState) 535 || isHomeService(oldRegState) != isHomeService(newRegState)) { 536 log("onServiceStateChanged: phone " + phoneId + " " 537 + NetworkRegistrationInfo.registrationStateToString(oldRegState) 538 + " -> " 539 + NetworkRegistrationInfo.registrationStateToString(newRegState)); 540 evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED); 541 } 542 } 543 } else { 544 loge("Unexpected null phone " + phoneId + " upon its registration state changed"); 545 } 546 } 547 548 /** @return {@code true} if the phone state is considered in service. */ 549 private static boolean isInService(@RegistrationState int dataRegState) { 550 return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME 551 || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; 552 } 553 554 /** @return {@code true} if the phone state is in home service. */ 555 private static boolean isHomeService(@RegistrationState int dataRegState) { 556 return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME; 557 } 558 559 /** 560 * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or 561 * override network types (5G NSA, 5G MMWAVE) change. 562 * @param phoneId The phone that changed. 563 */ 564 private void onDisplayInfoChanged(int phoneId) { 565 Phone phone = PhoneFactory.getPhone(phoneId); 566 if (phone != null && isActiveModemPhone(phoneId)) { 567 TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController() 568 .getTelephonyDisplayInfo(); 569 mPhonesSignalStatus[phoneId].mDisplayInfo = displayInfo; 570 if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) { 571 log("onDisplayInfoChanged: phone " + phoneId + " " + displayInfo); 572 evaluateAutoDataSwitch(EVALUATION_REASON_DISPLAY_INFO_CHANGED); 573 } 574 } else { 575 loge("Unexpected null phone " + phoneId + " upon its display info changed"); 576 } 577 } 578 579 /** 580 * Called when {@link SignalStrength} changed. 581 * @param phoneId The phone that changed. 582 */ 583 private void onSignalStrengthChanged(int phoneId) { 584 Phone phone = PhoneFactory.getPhone(phoneId); 585 if (phone != null && isActiveModemPhone(phoneId)) { 586 SignalStrength newSignalStrength = phone.getSignalStrength(); 587 SignalStrength oldSignalStrength = mPhonesSignalStatus[phoneId].mSignalStrength; 588 if (oldSignalStrength.getLevel() != newSignalStrength.getLevel()) { 589 mPhonesSignalStatus[phoneId].mSignalStrength = newSignalStrength; 590 if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) { 591 log("onSignalStrengthChanged: phone " + phoneId + " " 592 + oldSignalStrength.getLevel() + "->" + newSignalStrength.getLevel()); 593 evaluateAutoDataSwitch(EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED); 594 } 595 } 596 } else { 597 loge("Unexpected null phone " + phoneId + " upon its signal strength changed"); 598 } 599 } 600 601 /** 602 * Called as a preliminary check for the frequent signal/display info change. 603 * @return The phone Id if found a candidate phone with higher signal score, or the DDS has 604 * an equal score. 605 */ 606 private int getHigherScoreCandidatePhoneId() { 607 int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId(); 608 int ddsPhoneId = mSubscriptionManagerService.getPhoneId( 609 mSubscriptionManagerService.getDefaultDataSubId()); 610 if (isActiveModemPhone(preferredPhoneId) && isActiveModemPhone(ddsPhoneId)) { 611 int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore(); 612 for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { 613 if (phoneId == preferredPhoneId) continue; 614 int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore(); 615 if ((candidateScore - currentScore) > mScoreTolerance 616 // Also reevaluate if DDS has the same score as the current phone. 617 || (candidateScore >= currentScore && phoneId == ddsPhoneId)) { 618 return phoneId; 619 } 620 } 621 } 622 return INVALID_PHONE_INDEX; 623 } 624 625 /** 626 * Schedule for auto data switch evaluation. 627 * @param reason The reason for the evaluation. 628 */ 629 public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { 630 long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION 631 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold 632 << mAutoSwitchValidationFailedCount 633 : 0; 634 if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) { 635 scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason), 636 delayMs); 637 } 638 } 639 640 /** 641 * Evaluate for auto data switch opportunity. 642 * If suitable to switch, check that the suitable state is stable(or switch immediately if user 643 * turned off settings). 644 * @param reason The reason for the evaluation. 645 */ 646 private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { 647 // auto data switch feature is disabled. 648 if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; 649 int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); 650 // check is valid DSDS 651 if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return; 652 int defaultDataPhoneId = mSubscriptionManagerService.getPhoneId( 653 defaultDataSubId); 654 Phone defaultDataPhone = PhoneFactory.getPhone(defaultDataPhoneId); 655 if (defaultDataPhone == null) { 656 loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data" 657 + " subscription " + defaultDataSubId); 658 return; 659 } 660 661 int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId(); 662 StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:"); 663 debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId) 664 .append(" preferredPhoneId: ").append(preferredPhoneId) 665 .append(", reason: ").append(evaluationReasonToString(reason)); 666 if (preferredPhoneId == defaultDataPhoneId) { 667 // on default data sub 668 StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage); 669 log(debugMessage.toString()); 670 if (res.targetPhoneId != INVALID_PHONE_INDEX) { 671 mSelectedTargetPhoneId = res.targetPhoneId; 672 startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation); 673 } else { 674 cancelAnyPendingSwitch(); 675 } 676 } else { 677 // on backup data sub 678 Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId); 679 if (backupDataPhone == null || !isActiveModemPhone(preferredPhoneId)) { 680 loge(debugMessage.append(" Unexpected null phone ").append(preferredPhoneId) 681 .append(" as the current active data phone").toString()); 682 return; 683 } 684 685 DataEvaluation internetEvaluation; 686 if (sFeatureFlags.autoDataSwitchUsesDataEnabled()) { 687 if (!defaultDataPhone.isUserDataEnabled()) { 688 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, 689 EVALUATION_REASON_DATA_SETTINGS_CHANGED); 690 log(debugMessage.append( 691 ", immediately back to default as user turns off default").toString()); 692 return; 693 } else if (!(internetEvaluation = backupDataPhone.getDataNetworkController() 694 .getInternetEvaluation(false/*ignoreExistingNetworks*/)) 695 .isSubsetOf(DataEvaluation.DataDisallowedReason.NOT_IN_SERVICE)) { 696 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone( 697 DEFAULT_PHONE_INDEX, EVALUATION_REASON_DATA_SETTINGS_CHANGED); 698 log(debugMessage.append( 699 ", immediately back to default because backup ") 700 .append(internetEvaluation).toString()); 701 return; 702 } 703 } else { 704 if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) { 705 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, 706 EVALUATION_REASON_DATA_SETTINGS_CHANGED); 707 log(debugMessage.append( 708 ", immediately back to default as user turns off settings").toString()); 709 return; 710 } 711 } 712 713 boolean backToDefault = false; 714 boolean isForPerformance = false; 715 boolean needValidation = true; 716 717 if (isNddsRoamingEnabled()) { 718 if (mDefaultNetworkIsOnNonCellular) { 719 debugMessage.append(", back to default as default network") 720 .append(" is active on nonCellular transport"); 721 backToDefault = true; 722 needValidation = false; 723 } else { 724 PhoneSignalStatus.UsableState defaultUsableState = 725 mPhonesSignalStatus[defaultDataPhoneId].getUsableState(); 726 PhoneSignalStatus.UsableState currentUsableState = 727 mPhonesSignalStatus[preferredPhoneId].getUsableState(); 728 729 boolean isCurrentUsable = currentUsableState.mScore 730 > PhoneSignalStatus.UsableState.NOT_USABLE.mScore; 731 732 if (currentUsableState.mScore < defaultUsableState.mScore) { 733 debugMessage.append(", back to default phone ").append(preferredPhoneId) 734 .append(" : ").append(defaultUsableState) 735 .append(" , backup phone: ").append(currentUsableState); 736 737 backToDefault = true; 738 // Require validation if the current preferred phone is usable. 739 needValidation = isCurrentUsable && mRequirePingTestBeforeSwitch; 740 } else if (defaultUsableState.mScore == currentUsableState.mScore) { 741 debugMessage.append(", default phone ").append(preferredPhoneId) 742 .append(" : ").append(defaultUsableState) 743 .append(" , backup phone: ").append(currentUsableState); 744 745 if (isCurrentUsable) { 746 // Both phones are usable. 747 if (isRatSignalStrengthBasedSwitchEnabled()) { 748 int defaultScore = mPhonesSignalStatus[defaultDataPhoneId] 749 .getRatSignalScore(); 750 int currentScore = mPhonesSignalStatus[preferredPhoneId] 751 .getRatSignalScore(); 752 if (defaultScore >= currentScore) { 753 debugMessage 754 .append(", back to default for higher or equal score ") 755 .append(defaultScore).append(" versus current ") 756 .append(currentScore); 757 backToDefault = true; 758 isForPerformance = true; 759 needValidation = mRequirePingTestBeforeSwitch; 760 } 761 } else { 762 // Only OOS/in service switch is enabled, switch back. 763 debugMessage.append(", back to default as it's usable. "); 764 backToDefault = true; 765 needValidation = mRequirePingTestBeforeSwitch; 766 } 767 } else { 768 debugMessage.append(", back to default as both phones are unusable."); 769 backToDefault = true; 770 needValidation = false; 771 } 772 } 773 } 774 } else { 775 if (mDefaultNetworkIsOnNonCellular) { 776 debugMessage.append(", back to default as default network") 777 .append(" is active on nonCellular transport"); 778 backToDefault = true; 779 needValidation = false; 780 } else if (!isHomeService(mPhonesSignalStatus[preferredPhoneId].mDataRegState)) { 781 debugMessage.append(", back to default as backup phone lost HOME registration"); 782 backToDefault = true; 783 needValidation = false; 784 } else if (isRatSignalStrengthBasedSwitchEnabled()) { 785 int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore(); 786 int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore(); 787 if (defaultScore >= currentScore) { 788 debugMessage 789 .append(", back to default as default has higher or equal score ") 790 .append(defaultScore).append(" versus current ") 791 .append(currentScore); 792 backToDefault = true; 793 isForPerformance = true; 794 needValidation = mRequirePingTestBeforeSwitch; 795 } 796 } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) { 797 debugMessage.append(", back to default as the default is back to service "); 798 backToDefault = true; 799 needValidation = mRequirePingTestBeforeSwitch; 800 } 801 } 802 803 if (backToDefault) { 804 log(debugMessage.toString()); 805 mSelectedTargetPhoneId = defaultDataPhoneId; 806 startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation); 807 } else { 808 // cancel any previous attempts of switching back to default phone 809 cancelAnyPendingSwitch(); 810 } 811 } 812 } 813 814 /** 815 * Called when consider switching from primary default data sub to another data sub. 816 * @param defaultPhoneId The default data phone 817 * @param debugMessage Debug message. 818 * @return StabilityEventExtra As evaluation result. 819 */ 820 @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId, 821 @NonNull StringBuilder debugMessage) { 822 Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId); 823 boolean isForPerformance = false; 824 StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX, 825 isForPerformance, mRequirePingTestBeforeSwitch); 826 827 if (defaultDataPhone == null) { 828 debugMessage.append(", no candidate as no sim loaded"); 829 return invalidResult; 830 } 831 832 if (!defaultDataPhone.isUserDataEnabled()) { 833 debugMessage.append(", no candidate as user disabled mobile data"); 834 return invalidResult; 835 } 836 837 if (mDefaultNetworkIsOnNonCellular) { 838 debugMessage.append(", no candidate as default network is active") 839 .append(" on non-cellular transport"); 840 return invalidResult; 841 } 842 843 if (isNddsRoamingEnabled()) { 844 // check whether primary and secondary signal status are worth switching 845 if (!isRatSignalStrengthBasedSwitchEnabled() 846 && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) { 847 debugMessage.append(", no candidate as default phone is in HOME service"); 848 return invalidResult; 849 } 850 } else { 851 // check whether primary and secondary signal status are worth switching 852 if (!isRatSignalStrengthBasedSwitchEnabled() 853 && isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) { 854 debugMessage.append(", no candidate as default phone is in service"); 855 return invalidResult; 856 } 857 } 858 859 PhoneSignalStatus defaultPhoneStatus = mPhonesSignalStatus[defaultPhoneId]; 860 for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { 861 if (phoneId == defaultPhoneId) continue; 862 863 Phone secondaryDataPhone = null; 864 PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId]; 865 if (isNddsRoamingEnabled()) { 866 PhoneSignalStatus.UsableState currentUsableState = 867 mPhonesSignalStatus[defaultPhoneId].getUsableState(); 868 PhoneSignalStatus.UsableState candidateUsableState = 869 mPhonesSignalStatus[phoneId].getUsableState(); 870 debugMessage.append(", found phone ").append(phoneId).append(" ") 871 .append(candidateUsableState) 872 .append(", default is ").append(currentUsableState); 873 if (candidateUsableState.mScore > currentUsableState.mScore) { 874 secondaryDataPhone = PhoneFactory.getPhone(phoneId); 875 } else if (isRatSignalStrengthBasedSwitchEnabled() 876 && currentUsableState.mScore == candidateUsableState.mScore) { 877 // Both phones are home or both roaming enabled, so compare RAT/signal score. 878 879 int defaultScore = defaultPhoneStatus.getRatSignalScore(); 880 int candidateScore = candidatePhoneStatus.getRatSignalScore(); 881 if ((candidateScore - defaultScore) > mScoreTolerance) { 882 debugMessage.append(" with ").append(defaultScore) 883 .append(" versus candidate higher score ").append(candidateScore); 884 secondaryDataPhone = PhoneFactory.getPhone(phoneId); 885 isForPerformance = true; 886 } else { 887 debugMessage.append(", candidate's score ").append(candidateScore) 888 .append(" doesn't justify the switch given the current ") 889 .append(defaultScore); 890 } 891 } 892 } else if (isHomeService(candidatePhoneStatus.mDataRegState)) { 893 // the alternative phone must have HOME availability 894 debugMessage.append(", found phone ").append(phoneId).append(" in HOME service"); 895 896 if (isInService(defaultPhoneStatus.mDataRegState)) { 897 // Use score if RAT/signal strength based switch is enabled and both phone are 898 // in service. 899 if (isRatSignalStrengthBasedSwitchEnabled()) { 900 int defaultScore = mPhonesSignalStatus[defaultPhoneId].getRatSignalScore(); 901 int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore(); 902 if ((candidateScore - defaultScore) > mScoreTolerance) { 903 debugMessage.append(" with higher score ").append(candidateScore) 904 .append(" versus current ").append(defaultScore); 905 secondaryDataPhone = PhoneFactory.getPhone(phoneId); 906 isForPerformance = true; 907 } else { 908 debugMessage.append(", but its score ").append(candidateScore) 909 .append(" doesn't meet the bar to switch given the current ") 910 .append(defaultScore); 911 } 912 } 913 } else { 914 // Only OOS/in service switch is enabled. 915 secondaryDataPhone = PhoneFactory.getPhone(phoneId); 916 } 917 } 918 919 if (secondaryDataPhone != null) { 920 // check internet data is allowed on the candidate 921 DataEvaluation internetEvaluation = secondaryDataPhone.getDataNetworkController() 922 .getInternetEvaluation(false/*ignoreExistingNetworks*/); 923 if (!internetEvaluation.containsDisallowedReasons()) { 924 return new StabilityEventExtra(phoneId, 925 isForPerformance, mRequirePingTestBeforeSwitch); 926 } else { 927 debugMessage.append(", but candidate's data is not allowed ") 928 .append(internetEvaluation); 929 } 930 } 931 } 932 debugMessage.append(", found no qualified candidate."); 933 return invalidResult; 934 } 935 936 /** 937 * @return {@code true} If the feature of switching base on RAT and signal strength is enabled. 938 */ 939 private boolean isRatSignalStrengthBasedSwitchEnabled() { 940 return mScoreTolerance >= 0 && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0; 941 } 942 943 /** 944 * @return {@code true} If the feature of switching to roaming non DDS is enabled. 945 */ 946 private boolean isNddsRoamingEnabled() { 947 return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoaming; 948 } 949 950 /** 951 * Called when the current environment suits auto data switch. 952 * Start pre-switch validation if the current environment suits auto data switch for 953 * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS. 954 * @param targetPhoneId the target phone Id. 955 * @param isForPerformance {@code true} entails longer stability check. 956 * @param needValidation {@code true} if validation is needed. 957 */ 958 private void startStabilityCheck(int targetPhoneId, boolean isForPerformance, 959 boolean needValidation) { 960 StabilityEventExtra eventExtras = (StabilityEventExtra) 961 mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED, 962 new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/, 963 false /*isForPerformance*/)); 964 long delayMs = -1; 965 // Check if already scheduled one with that combination of extras. 966 if (eventExtras.targetPhoneId != targetPhoneId 967 || eventExtras.needValidation != needValidation 968 || eventExtras.isForPerformance != isForPerformance) { 969 eventExtras = 970 new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation); 971 972 // Reset with new timer. 973 delayMs = isForPerformance 974 ? mAutoDataSwitchPerformanceStabilityTimeThreshold 975 : mAutoDataSwitchAvailabilityStabilityTimeThreshold; 976 scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs); 977 } 978 log("startStabilityCheck: " 979 + (delayMs != -1 ? "scheduling " : "already scheduled ") 980 + eventExtras); 981 } 982 983 /** 984 * Use when need to schedule with timer. Short timer uses handler, while the longer timer uses 985 * alarm manager to account for real time elapse. 986 * 987 * @param event The event. 988 * @param extras Any extra data associated with the event. 989 * @param delayMs The delayed interval in ms. 990 */ 991 private void scheduleEventWithTimer(int event, @NonNull Object extras, long delayMs) { 992 // Get singleton alarm listener. 993 mEventsToAlarmListener.putIfAbsent(event, () -> sendEmptyMessage(event)); 994 AlarmManager.OnAlarmListener listener = mEventsToAlarmListener.get(event); 995 996 // Cancel any existing. 997 removeMessages(event); 998 mAlarmManager.cancel(listener); 999 // Override with new extras. 1000 mScheduledEventsToExtras.put(event, extras); 1001 // Reset timer. 1002 if (delayMs <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) { 1003 // Use handler for short timer. 1004 sendEmptyMessageDelayed(event, delayMs); 1005 } else { 1006 // Not using setWhileIdle because it can wait util the next time the device wakes up to 1007 // save power. 1008 // If another evaluation is processed before the alarm fires, 1009 // this timer is restarted (AlarmManager using the same listener resets the 1010 // timer). 1011 mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 1012 SystemClock.elapsedRealtime() + delayMs, 1013 LOG_TAG /*debug tag*/, listener, this); 1014 } 1015 } 1016 1017 /** Auto data switch evaluation reason to string. */ 1018 @NonNull 1019 public static String evaluationReasonToString( 1020 @AutoDataSwitchEvaluationReason int reason) { 1021 return switch (reason) { 1022 case EVALUATION_REASON_REGISTRATION_STATE_CHANGED -> "REGISTRATION_STATE_CHANGED"; 1023 case EVALUATION_REASON_DISPLAY_INFO_CHANGED -> "DISPLAY_INFO_CHANGED"; 1024 case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED -> "SIGNAL_STRENGTH_CHANGED"; 1025 case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED -> "DEFAULT_NETWORK_CHANGED"; 1026 case EVALUATION_REASON_DATA_SETTINGS_CHANGED -> "DATA_SETTINGS_CHANGED"; 1027 case EVALUATION_REASON_RETRY_VALIDATION -> "RETRY_VALIDATION"; 1028 case EVALUATION_REASON_SIM_LOADED -> "SIM_LOADED"; 1029 case EVALUATION_REASON_VOICE_CALL_END -> "VOICE_CALL_END"; 1030 default -> "Unknown(" + reason + ")"; 1031 }; 1032 } 1033 1034 /** @return {@code true} if the sub is active. */ 1035 private boolean isActiveSubId(int subId) { 1036 SubscriptionInfoInternal subInfo = mSubscriptionManagerService 1037 .getSubscriptionInfoInternal(subId); 1038 return subInfo != null && subInfo.isActive(); 1039 } 1040 1041 /** 1042 * Called when default network capabilities changed. If default network is active on 1043 * non-cellular, switch back to the default data phone. If default network is lost, try to find 1044 * another sub to switch to. 1045 * @param networkCapabilities {@code null} indicates default network lost. 1046 */ 1047 public void updateDefaultNetworkCapabilities( 1048 @Nullable NetworkCapabilities networkCapabilities) { 1049 if (networkCapabilities != null) { 1050 // Exists default network 1051 mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR); 1052 if (mDefaultNetworkIsOnNonCellular 1053 && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) { 1054 log("default network is active on non cellular, switch back to default"); 1055 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); 1056 } 1057 } else { 1058 log("default network is lost, try to find another active sub to switch to"); 1059 mDefaultNetworkIsOnNonCellular = false; 1060 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); 1061 } 1062 } 1063 1064 /** 1065 * Cancel any auto switch attempts when the current environment is not suitable for auto switch. 1066 */ 1067 private void cancelAnyPendingSwitch() { 1068 mSelectedTargetPhoneId = INVALID_PHONE_INDEX; 1069 resetFailedCount(); 1070 if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) { 1071 if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) { 1072 mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED)); 1073 } else { 1074 loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null"); 1075 } 1076 removeMessages(EVENT_STABILITY_CHECK_PASSED); 1077 mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED); 1078 } 1079 mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation(); 1080 } 1081 1082 /** 1083 * Display a notification the first time auto data switch occurs. 1084 * @param phoneId The phone Id of the current preferred phone. 1085 * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature. 1086 */ 1087 public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) { 1088 NotificationManager notificationManager = mContext.getSystemService( 1089 NotificationManager.class); 1090 if (notificationManager == null) return; 1091 if (mDisplayedNotification) { 1092 // cancel posted notification if any exist 1093 notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG, 1094 AUTO_DATA_SWITCH_NOTIFICATION_ID); 1095 return; 1096 } 1097 // proceed only the first time auto data switch occurs, which includes data during call 1098 if (!isDueToAutoSwitch) { 1099 return; 1100 } 1101 SubscriptionInfo subInfo = mSubscriptionManagerService 1102 .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId)); 1103 if (subInfo == null || subInfo.isOpportunistic()) { 1104 loge("displayAutoDataSwitchNotification: phoneId=" 1105 + phoneId + " unexpected subInfo " + subInfo); 1106 return; 1107 } 1108 int subId = subInfo.getSubscriptionId(); 1109 logl("displayAutoDataSwitchNotification: display for subId=" + subId); 1110 // "Mobile network settings" screen / dialog 1111 Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); 1112 final Bundle fragmentArgs = new Bundle(); 1113 // Special contract for Settings to highlight permission row 1114 fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); 1115 intent.putExtra(Settings.EXTRA_SUB_ID, subId); 1116 intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); 1117 PendingIntent contentIntent = PendingIntent.getActivity( 1118 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE); 1119 1120 CharSequence activeCarrierName = subInfo.getDisplayName(); 1121 CharSequence contentTitle = mContext.getString( 1122 com.android.internal.R.string.auto_data_switch_title, activeCarrierName); 1123 CharSequence contentText = mContext.getText( 1124 com.android.internal.R.string.auto_data_switch_content); 1125 1126 final Notification notif = new Notification.Builder(mContext) 1127 .setContentTitle(contentTitle) 1128 .setContentText(contentText) 1129 .setSmallIcon(android.R.drawable.stat_sys_warning) 1130 .setColor(mContext.getResources().getColor( 1131 com.android.internal.R.color.system_notification_accent_color)) 1132 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) 1133 .setContentIntent(contentIntent) 1134 .setStyle(new Notification.BigTextStyle().bigText(contentText)) 1135 .build(); 1136 notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG, 1137 AUTO_DATA_SWITCH_NOTIFICATION_ID, notif); 1138 mDisplayedNotification = true; 1139 } 1140 1141 /** Enable future switch retry again. Called when switch condition changed. */ 1142 public void resetFailedCount() { 1143 mAutoSwitchValidationFailedCount = 0; 1144 } 1145 1146 /** 1147 * Called when skipped switch due to validation failed. Schedule retry to switch again. 1148 */ 1149 public void evaluateRetryOnValidationFailed() { 1150 if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) { 1151 evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION); 1152 mAutoSwitchValidationFailedCount++; 1153 } else { 1154 logl("evaluateRetryOnValidationFailed: reached max auto switch retry count " 1155 + mAutoDataSwitchValidationMaxRetry); 1156 mAutoSwitchValidationFailedCount = 0; 1157 } 1158 } 1159 1160 /** 1161 * @param phoneId The phone Id to check. 1162 * @return {@code true} if the phone Id is an active modem. 1163 */ 1164 private boolean isActiveModemPhone(int phoneId) { 1165 return phoneId >= 0 && phoneId < mPhonesSignalStatus.length; 1166 } 1167 1168 /** 1169 * Log debug messages. 1170 * @param s debug messages 1171 */ 1172 private void log(@NonNull String s) { 1173 Rlog.d(LOG_TAG, s); 1174 } 1175 1176 /** 1177 * Log error messages. 1178 * @param s error messages 1179 */ 1180 private void loge(@NonNull String s) { 1181 Rlog.e(LOG_TAG, s); 1182 } 1183 1184 /** 1185 * Log debug messages and also log into the local log. 1186 * @param s debug messages 1187 */ 1188 private void logl(@NonNull String s) { 1189 log(s); 1190 mLocalLog.log(s); 1191 } 1192 1193 /** 1194 * Dump the state of DataNetworkController 1195 * 1196 * @param fd File descriptor 1197 * @param printWriter Print writer 1198 * @param args Arguments 1199 */ 1200 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 1201 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 1202 pw.println("AutoDataSwitchController:"); 1203 pw.increaseIndent(); 1204 pw.println("mScoreTolerance=" + mScoreTolerance); 1205 pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry 1206 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount); 1207 pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch); 1208 pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" 1209 + mAutoDataSwitchAvailabilityStabilityTimeThreshold); 1210 pw.println("mSelectedTargetPhoneId=" + mSelectedTargetPhoneId); 1211 pw.increaseIndent(); 1212 for (PhoneSignalStatus status: mPhonesSignalStatus) { 1213 pw.println(status); 1214 } 1215 pw.decreaseIndent(); 1216 mLocalLog.dump(fd, pw, args); 1217 pw.decreaseIndent(); 1218 } 1219 } 1220