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