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.Notification; 27 import android.app.NotificationManager; 28 import android.app.PendingIntent; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.net.NetworkCapabilities; 32 import android.os.AsyncResult; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.provider.Settings; 38 import android.telephony.AccessNetworkConstants; 39 import android.telephony.NetworkRegistrationInfo; 40 import android.telephony.SignalStrength; 41 import android.telephony.SubscriptionInfo; 42 import android.telephony.SubscriptionManager; 43 import android.telephony.TelephonyDisplayInfo; 44 import android.util.IndentingPrintWriter; 45 import android.util.LocalLog; 46 47 import com.android.internal.telephony.Phone; 48 import com.android.internal.telephony.PhoneFactory; 49 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 50 import com.android.internal.telephony.subscription.SubscriptionManagerService; 51 import com.android.internal.telephony.util.NotificationChannelController; 52 import com.android.telephony.Rlog; 53 54 55 import java.io.FileDescriptor; 56 import java.io.PrintWriter; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.Arrays; 60 61 /** 62 * Recommend a data phone to use based on its availability. 63 */ 64 public class AutoDataSwitchController extends Handler { 65 /** Registration state changed. */ 66 public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1; 67 /** Telephony Display Info changed. */ 68 public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2; 69 /** Signal Strength changed. */ 70 public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3; 71 /** Default network capabilities changed or lost. */ 72 public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4; 73 /** Data enabled settings changed. */ 74 public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5; 75 /** Retry due to previous validation failed. */ 76 public static final int EVALUATION_REASON_RETRY_VALIDATION = 6; 77 /** Sim loaded which means slot mapping became available. */ 78 public static final int EVALUATION_REASON_SIM_LOADED = 7; 79 /** Voice call ended. */ 80 public static final int EVALUATION_REASON_VOICE_CALL_END = 8; 81 @Retention(RetentionPolicy.SOURCE) 82 @IntDef(prefix = "EVALUATION_REASON_", 83 value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED, 84 EVALUATION_REASON_DISPLAY_INFO_CHANGED, 85 EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED, 86 EVALUATION_REASON_DEFAULT_NETWORK_CHANGED, 87 EVALUATION_REASON_DATA_SETTINGS_CHANGED, 88 EVALUATION_REASON_RETRY_VALIDATION, 89 EVALUATION_REASON_SIM_LOADED, 90 EVALUATION_REASON_VOICE_CALL_END}) 91 public @interface AutoDataSwitchEvaluationReason {} 92 93 private static final String LOG_TAG = "ADSC"; 94 95 /** Event for service state changed. */ 96 private static final int EVENT_SERVICE_STATE_CHANGED = 1; 97 /** Event for display info changed. This is for getting 5G NSA or mmwave information. */ 98 private static final int EVENT_DISPLAY_INFO_CHANGED = 2; 99 /** Event for evaluate auto data switch opportunity. */ 100 private static final int EVENT_EVALUATE_AUTO_SWITCH = 3; 101 /** Event for signal strength changed. */ 102 private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4; 103 /** Event indicates the switch state is stable, proceed to validation as the next step. */ 104 private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5; 105 106 /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */ 107 private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 108 /** 109 * When starting this activity, this extra can also be specified to supply a Bundle of arguments 110 * to pass to that fragment when it is instantiated during the initial creation of the activity. 111 */ 112 private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = 113 ":settings:show_fragment_args"; 114 /** The resource ID of the auto data switch fragment in settings. **/ 115 private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; 116 /** Notification tag **/ 117 private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch"; 118 /** Notification ID **/ 119 private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1; 120 121 private final @NonNull LocalLog mLocalLog = new LocalLog(128); 122 private final @NonNull Context mContext; 123 private final @NonNull SubscriptionManagerService mSubscriptionManagerService; 124 private final @NonNull PhoneSwitcher mPhoneSwitcher; 125 private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback; 126 private boolean mDefaultNetworkIsOnNonCellular = false; 127 /** {@code true} if we've displayed the notification the first time auto switch occurs **/ 128 private boolean mDisplayedNotification = false; 129 /** 130 * Time threshold in ms to define a internet connection status to be stable(e.g. out of service, 131 * in service, wifi is the default active network.etc), while -1 indicates auto switch 132 * feature disabled. 133 */ 134 private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1; 135 /** 136 * {@code true} if requires ping test before switching preferred data modem; otherwise, switch 137 * even if ping test fails. 138 */ 139 private boolean mRequirePingTestBeforeSwitch = true; 140 /** The count of consecutive auto switch validation failure **/ 141 private int mAutoSwitchValidationFailedCount = 0; 142 /** 143 * The maximum number of retries when a validation for switching failed. 144 */ 145 private int mAutoDataSwitchValidationMaxRetry; 146 147 private @NonNull PhoneSignalStatus[] mPhonesSignalStatus; 148 149 /** 150 * To track the signal status of a phone in order to evaluate whether it's a good candidate to 151 * switch to. 152 */ 153 private static class PhoneSignalStatus { 154 private @NonNull Phone mPhone; 155 private @NetworkRegistrationInfo.RegistrationState int mDataRegState = 156 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; 157 private @NonNull TelephonyDisplayInfo mDisplayInfo; 158 private @NonNull SignalStrength mSignalStrength; 159 160 private int mScore; 161 PhoneSignalStatus(@onNull Phone phone)162 private PhoneSignalStatus(@NonNull Phone phone) { 163 this.mPhone = phone; 164 this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo(); 165 this.mSignalStrength = phone.getSignalStrength(); 166 } updateScore()167 private int updateScore() { 168 // TODO: score = inservice? dcm.getscore() : 0 169 return mScore; 170 } 171 @Override toString()172 public String toString() { 173 return "{phoneId=" + mPhone.getPhoneId() 174 + " score=" + mScore + " dataRegState=" 175 + NetworkRegistrationInfo.registrationStateToString(mDataRegState) 176 + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel() 177 + "}"; 178 179 } 180 } 181 182 /** 183 * This is the callback used for listening events from {@link AutoDataSwitchController}. 184 */ 185 public abstract static class AutoDataSwitchControllerCallback { 186 /** 187 * Called when a target data phone is recommended by the controller. 188 * @param targetPhoneId The target phone Id. 189 * @param needValidation {@code true} if need a ping test to pass before switching. 190 */ onRequireValidation(int targetPhoneId, boolean needValidation)191 public abstract void onRequireValidation(int targetPhoneId, boolean needValidation); 192 193 /** 194 * Called when a target data phone is demanded by the controller. 195 * @param targetPhoneId The target phone Id. 196 * @param reason The reason for the demand. 197 */ onRequireImmediatelySwitchToPhone(int targetPhoneId, @AutoDataSwitchEvaluationReason int reason)198 public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId, 199 @AutoDataSwitchEvaluationReason int reason); 200 201 /** 202 * Called when the controller asks to cancel any pending validation attempts because the 203 * environment is no longer suited for switching. 204 */ onRequireCancelAnyPendingAutoSwitchValidation()205 public abstract void onRequireCancelAnyPendingAutoSwitchValidation(); 206 } 207 208 /** 209 * @param context Context. 210 * @param looper Main looper. 211 * @param phoneSwitcher Phone switcher. 212 * @param phoneSwitcherCallback Callback for phone switcher to execute. 213 */ AutoDataSwitchController(@onNull Context context, @NonNull Looper looper, @NonNull PhoneSwitcher phoneSwitcher, @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback)214 public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper, 215 @NonNull PhoneSwitcher phoneSwitcher, 216 @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) { 217 super(looper); 218 mContext = context; 219 mSubscriptionManagerService = SubscriptionManagerService.getInstance(); 220 mPhoneSwitcher = phoneSwitcher; 221 mPhoneSwitcherCallback = phoneSwitcherCallback; 222 readDeviceResourceConfig(); 223 int numActiveModems = PhoneFactory.getPhones().length; 224 mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems]; 225 for (int phoneId = 0; phoneId < numActiveModems; phoneId++) { 226 registerAllEventsForPhone(phoneId); 227 } 228 } 229 230 /** 231 * Called when active modem count changed, update all tracking events. 232 * @param numActiveModems The current number of active modems. 233 */ onMultiSimConfigChanged(int numActiveModems)234 public synchronized void onMultiSimConfigChanged(int numActiveModems) { 235 int oldActiveModems = mPhonesSignalStatus.length; 236 if (oldActiveModems == numActiveModems) return; 237 // Dual -> Single 238 for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) { 239 Phone phone = mPhonesSignalStatus[phoneId].mPhone; 240 phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this); 241 phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this); 242 phone.getServiceStateTracker().unregisterForServiceStateChanged(this); 243 } 244 mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems); 245 // Signal -> Dual 246 for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) { 247 registerAllEventsForPhone(phoneId); 248 } 249 } 250 251 /** 252 * Register all tracking events for a phone. 253 * @param phoneId The phone to register for all events. 254 */ registerAllEventsForPhone(int phoneId)255 private void registerAllEventsForPhone(int phoneId) { 256 Phone phone = PhoneFactory.getPhone(phoneId); 257 if (phone != null) { 258 mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone); 259 phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged( 260 this, EVENT_DISPLAY_INFO_CHANGED, phoneId); 261 phone.getSignalStrengthController().registerForSignalStrengthChanged( 262 this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId); 263 phone.getServiceStateTracker().registerForServiceStateChanged(this, 264 EVENT_SERVICE_STATE_CHANGED, phoneId); 265 } else { 266 loge("Unexpected null phone " + phoneId + " when register all events"); 267 } 268 } 269 270 /** 271 * Read the default device config from any default phone because the resource config are per 272 * device. No need to register callback for the same reason. 273 */ readDeviceResourceConfig()274 private void readDeviceResourceConfig() { 275 Phone phone = PhoneFactory.getDefaultPhone(); 276 DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); 277 mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); 278 mAutoDataSwitchAvailabilityStabilityTimeThreshold = 279 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); 280 mAutoDataSwitchValidationMaxRetry = 281 dataConfig.getAutoDataSwitchValidationMaxRetry(); 282 } 283 284 @Override handleMessage(@onNull Message msg)285 public void handleMessage(@NonNull Message msg) { 286 AsyncResult ar; 287 int phoneId; 288 switch (msg.what) { 289 case EVENT_SERVICE_STATE_CHANGED: 290 ar = (AsyncResult) msg.obj; 291 phoneId = (int) ar.userObj; 292 onRegistrationStateChanged(phoneId); 293 break; 294 case EVENT_DISPLAY_INFO_CHANGED: 295 ar = (AsyncResult) msg.obj; 296 phoneId = (int) ar.userObj; 297 onDisplayInfoChanged(phoneId); 298 break; 299 case EVENT_EVALUATE_AUTO_SWITCH: 300 int reason = (int) msg.obj; 301 onEvaluateAutoDataSwitch(reason); 302 break; 303 case EVENT_MEETS_AUTO_DATA_SWITCH_STATE: 304 int targetPhoneId = msg.arg1; 305 boolean needValidation = (boolean) msg.obj; 306 log("require validation on phone " + targetPhoneId 307 + (needValidation ? "" : " no") + " need to pass"); 308 mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation); 309 break; 310 default: 311 loge("Unexpected event " + msg.what); 312 } 313 } 314 315 /** 316 * Called when registration state changed. 317 */ onRegistrationStateChanged(int phoneId)318 private void onRegistrationStateChanged(int phoneId) { 319 Phone phone = PhoneFactory.getPhone(phoneId); 320 if (phone != null) { 321 int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState; 322 int newRegState = phone.getServiceState() 323 .getNetworkRegistrationInfo( 324 NetworkRegistrationInfo.DOMAIN_PS, 325 AccessNetworkConstants.TRANSPORT_TYPE_WWAN) 326 .getRegistrationState(); 327 if (newRegState != oldRegState) { 328 mPhonesSignalStatus[phoneId].mDataRegState = newRegState; 329 log("onRegistrationStateChanged: phone " + phoneId + " " 330 + NetworkRegistrationInfo.registrationStateToString(oldRegState) 331 + " -> " 332 + NetworkRegistrationInfo.registrationStateToString(newRegState)); 333 evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED); 334 } else { 335 log("onRegistrationStateChanged: no change."); 336 } 337 } else { 338 loge("Unexpected null phone " + phoneId + " upon its registration state changed"); 339 } 340 } 341 342 /** 343 * @return {@code true} if the phone state is considered in service. 344 */ isInService(@etworkRegistrationInfo.RegistrationState int dataRegState)345 private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) { 346 return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME 347 || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; 348 } 349 350 /** 351 * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or 352 * override network types (5G NSA, 5G MMWAVE) change. 353 */ onDisplayInfoChanged(int phoneId)354 private void onDisplayInfoChanged(int phoneId) { 355 Phone phone = PhoneFactory.getPhone(phoneId); 356 if (phone != null) { 357 TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController() 358 .getTelephonyDisplayInfo(); 359 //TODO(b/260928808) 360 log("onDisplayInfoChanged:" + displayInfo); 361 } else { 362 loge("Unexpected null phone " + phoneId + " upon its display info changed"); 363 } 364 } 365 366 /** 367 * Schedule for auto data switch evaluation. 368 * @param reason The reason for the evaluation. 369 */ evaluateAutoDataSwitch(@utoDataSwitchEvaluationReason int reason)370 public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { 371 long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION 372 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold 373 << mAutoSwitchValidationFailedCount 374 : 0; 375 if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { 376 sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs); 377 } 378 } 379 380 /** 381 * Evaluate for auto data switch opportunity. 382 * If suitable to switch, check that the suitable state is stable(or switch immediately if user 383 * turned off settings). 384 * @param reason The reason for the evaluation. 385 */ onEvaluateAutoDataSwitch(@utoDataSwitchEvaluationReason int reason)386 private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { 387 // auto data switch feature is disabled. 388 if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; 389 int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); 390 // check is valid DSDS 391 if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService 392 .getActiveSubIdList(true).length <= 1) { 393 return; 394 } 395 Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId( 396 defaultDataSubId)); 397 if (defaultDataPhone == null) { 398 loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data" 399 + " subscription " + defaultDataSubId); 400 return; 401 } 402 int defaultDataPhoneId = defaultDataPhone.getPhoneId(); 403 int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId(); 404 log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId 405 + " preferredPhoneId: " + preferredPhoneId 406 + " reason: " + evaluationReasonToString(reason)); 407 if (preferredPhoneId == defaultDataPhoneId) { 408 // on default data sub 409 int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId); 410 if (candidatePhoneId != INVALID_PHONE_INDEX) { 411 startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch); 412 } else { 413 cancelAnyPendingSwitch(); 414 } 415 } else { 416 // on backup data sub 417 Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId); 418 if (backupDataPhone == null) { 419 loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId 420 + " as the current active data phone"); 421 return; 422 } 423 424 if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) { 425 // immediately switch back if user disabled setting changes 426 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, 427 EVALUATION_REASON_DATA_SETTINGS_CHANGED); 428 return; 429 } 430 431 if (mDefaultNetworkIsOnNonCellular) { 432 log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport"); 433 startStabilityCheck(DEFAULT_PHONE_INDEX, false); 434 return; 435 } 436 437 if (mPhonesSignalStatus[preferredPhoneId].mDataRegState 438 != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { 439 // backup phone lost its HOME registration 440 startStabilityCheck(DEFAULT_PHONE_INDEX, false); 441 return; 442 } 443 444 if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) { 445 // default phone is back to service 446 startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch); 447 return; 448 } 449 450 // cancel any previous attempts of switching back to default phone 451 cancelAnyPendingSwitch(); 452 } 453 } 454 455 /** 456 * Called when consider switching from primary default data sub to another data sub. 457 * @return the target subId if a suitable candidate is found, otherwise return 458 * {@link SubscriptionManager#INVALID_PHONE_INDEX} 459 */ getSwitchCandidatePhoneId(int defaultPhoneId)460 private int getSwitchCandidatePhoneId(int defaultPhoneId) { 461 Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId); 462 if (defaultDataPhone == null) { 463 log("getSwitchCandidatePhoneId: no sim loaded"); 464 return INVALID_PHONE_INDEX; 465 } 466 467 if (!defaultDataPhone.isUserDataEnabled()) { 468 log("getSwitchCandidatePhoneId: user disabled data"); 469 return INVALID_PHONE_INDEX; 470 } 471 472 if (mDefaultNetworkIsOnNonCellular) { 473 // Exists other active default transport 474 log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport"); 475 return INVALID_PHONE_INDEX; 476 } 477 478 // check whether primary and secondary signal status are worth switching 479 if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) { 480 log("getSwitchCandidatePhoneId: DDS is in service"); 481 return INVALID_PHONE_INDEX; 482 } 483 for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { 484 if (phoneId != defaultPhoneId) { 485 // the alternative phone must have HOME availability 486 if (mPhonesSignalStatus[phoneId].mDataRegState 487 == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { 488 log("getSwitchCandidatePhoneId: found phone " + phoneId 489 + " in HOME service"); 490 Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId); 491 if (secondaryDataPhone != null && // check auto switch feature enabled 492 secondaryDataPhone.isDataAllowed()) { 493 return phoneId; 494 } 495 } 496 } 497 } 498 return INVALID_PHONE_INDEX; 499 } 500 501 /** 502 * Called when the current environment suits auto data switch. 503 * Start pre-switch validation if the current environment suits auto data switch for 504 * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS. 505 * @param targetPhoneId the target phone Id. 506 * @param needValidation {@code true} if validation is needed. 507 */ startStabilityCheck(int targetPhoneId, boolean needValidation)508 private void startStabilityCheck(int targetPhoneId, boolean needValidation) { 509 log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId 510 + " needValidation=" + needValidation); 511 if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) { 512 sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId, 513 0/*placeholder*/, 514 needValidation), 515 mAutoDataSwitchAvailabilityStabilityTimeThreshold); 516 } 517 } 518 519 /** Auto data switch evaluation reason to string. */ evaluationReasonToString( @utoDataSwitchEvaluationReason int reason)520 public static @NonNull String evaluationReasonToString( 521 @AutoDataSwitchEvaluationReason int reason) { 522 switch (reason) { 523 case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED"; 524 case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED"; 525 case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED"; 526 case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED"; 527 case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED"; 528 case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION"; 529 case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED"; 530 case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END"; 531 } 532 return "Unknown(" + reason + ")"; 533 } 534 535 /** @return {@code true} if the sub is active. */ isActiveSubId(int subId)536 private boolean isActiveSubId(int subId) { 537 SubscriptionInfoInternal subInfo = mSubscriptionManagerService 538 .getSubscriptionInfoInternal(subId); 539 return subInfo != null && subInfo.isActive(); 540 } 541 542 /** 543 * Called when default network capabilities changed. If default network is active on 544 * non-cellular, switch back to the default data phone. If default network is lost, try to find 545 * another sub to switch to. 546 * @param networkCapabilities {@code null} indicates default network lost. 547 */ updateDefaultNetworkCapabilities( @ullable NetworkCapabilities networkCapabilities)548 public void updateDefaultNetworkCapabilities( 549 @Nullable NetworkCapabilities networkCapabilities) { 550 if (networkCapabilities != null) { 551 // Exists default network 552 mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR); 553 if (mDefaultNetworkIsOnNonCellular 554 && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) { 555 log("default network is active on non cellular, switch back to default"); 556 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); 557 } 558 } else { 559 log("default network is lost, try to find another active sub to switch to"); 560 mDefaultNetworkIsOnNonCellular = false; 561 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); 562 } 563 } 564 565 /** 566 * Cancel any auto switch attempts when the current environment is not suitable for auto switch. 567 */ cancelAnyPendingSwitch()568 private void cancelAnyPendingSwitch() { 569 resetFailedCount(); 570 removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE); 571 mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation(); 572 } 573 574 /** 575 * Display a notification the first time auto data switch occurs. 576 * @param phoneId The phone Id of the current preferred phone. 577 * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature. 578 */ displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch)579 public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) { 580 NotificationManager notificationManager = (NotificationManager) 581 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 582 if (mDisplayedNotification) { 583 // cancel posted notification if any exist 584 log("displayAutoDataSwitchNotification: canceling any notifications for phone " 585 + phoneId); 586 notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG, 587 AUTO_DATA_SWITCH_NOTIFICATION_ID); 588 return; 589 } 590 // proceed only the first time auto data switch occurs, which includes data during call 591 if (!isDueToAutoSwitch) { 592 return; 593 } 594 SubscriptionInfo subInfo = mSubscriptionManagerService 595 .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId)); 596 if (subInfo == null || subInfo.isOpportunistic()) { 597 loge("displayAutoDataSwitchNotification: phoneId=" 598 + phoneId + " unexpected subInfo " + subInfo); 599 return; 600 } 601 int subId = subInfo.getSubscriptionId(); 602 logl("displayAutoDataSwitchNotification: display for subId=" + subId); 603 // "Mobile network settings" screen / dialog 604 Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); 605 final Bundle fragmentArgs = new Bundle(); 606 // Special contract for Settings to highlight permission row 607 fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); 608 intent.putExtra(Settings.EXTRA_SUB_ID, subId); 609 intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); 610 PendingIntent contentIntent = PendingIntent.getActivity( 611 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE); 612 613 CharSequence activeCarrierName = subInfo.getDisplayName(); 614 CharSequence contentTitle = mContext.getString( 615 com.android.internal.R.string.auto_data_switch_title, activeCarrierName); 616 CharSequence contentText = mContext.getText( 617 com.android.internal.R.string.auto_data_switch_content); 618 619 final Notification notif = new Notification.Builder(mContext) 620 .setContentTitle(contentTitle) 621 .setContentText(contentText) 622 .setSmallIcon(android.R.drawable.stat_sys_warning) 623 .setColor(mContext.getResources().getColor( 624 com.android.internal.R.color.system_notification_accent_color)) 625 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) 626 .setContentIntent(contentIntent) 627 .setStyle(new Notification.BigTextStyle().bigText(contentText)) 628 .build(); 629 notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG, 630 AUTO_DATA_SWITCH_NOTIFICATION_ID, notif); 631 mDisplayedNotification = true; 632 } 633 634 /** Enable future switch retry again. Called when switch condition changed. */ resetFailedCount()635 public void resetFailedCount() { 636 mAutoSwitchValidationFailedCount = 0; 637 } 638 639 /** 640 * Called when skipped switch due to validation failed. Schedule retry to switch again. 641 */ evaluateRetryOnValidationFailed()642 public void evaluateRetryOnValidationFailed() { 643 if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) { 644 evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION); 645 mAutoSwitchValidationFailedCount++; 646 } else { 647 logl("evaluateRetryOnValidationFailed: reached max auto switch retry count " 648 + mAutoDataSwitchValidationMaxRetry); 649 mAutoSwitchValidationFailedCount = 0; 650 } 651 } 652 653 /** 654 * Log debug messages. 655 * @param s debug messages 656 */ log(@onNull String s)657 private void log(@NonNull String s) { 658 Rlog.d(LOG_TAG, s); 659 } 660 661 /** 662 * Log error messages. 663 * @param s error messages 664 */ loge(@onNull String s)665 private void loge(@NonNull String s) { 666 Rlog.e(LOG_TAG, s); 667 } 668 669 /** 670 * Log debug messages and also log into the local log. 671 * @param s debug messages 672 */ logl(@onNull String s)673 private void logl(@NonNull String s) { 674 log(s); 675 mLocalLog.log(s); 676 } 677 678 /** 679 * Dump the state of DataNetworkController 680 * 681 * @param fd File descriptor 682 * @param printWriter Print writer 683 * @param args Arguments 684 */ dump(FileDescriptor fd, PrintWriter printWriter, String[] args)685 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 686 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 687 pw.println("AutoDataSwitchController:"); 688 pw.increaseIndent(); 689 pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry 690 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount); 691 pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch); 692 pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" 693 + mAutoDataSwitchAvailabilityStabilityTimeThreshold); 694 pw.increaseIndent(); 695 for (PhoneSignalStatus status: mPhonesSignalStatus) { 696 pw.println(status); 697 } 698 pw.decreaseIndent(); 699 mLocalLog.dump(fd, pw, args); 700 pw.decreaseIndent(); 701 } 702 } 703