1 /* 2 * Copyright 2021 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.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.ElapsedRealtimeLong; 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.content.Intent; 26 import android.database.ContentObserver; 27 import android.net.NetworkAgent; 28 import android.net.NetworkCapabilities; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.SystemClock; 35 import android.provider.Settings; 36 import android.telephony.Annotation.RadioPowerState; 37 import android.telephony.Annotation.ValidationStatus; 38 import android.telephony.CellSignalStrength; 39 import android.telephony.SubscriptionManager; 40 import android.telephony.TelephonyManager; 41 import android.text.TextUtils; 42 import android.util.IndentingPrintWriter; 43 import android.util.LocalLog; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.telephony.Phone; 47 import com.android.internal.telephony.PhoneConstants; 48 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback; 49 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; 50 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; 51 import com.android.internal.telephony.flags.FeatureFlags; 52 import com.android.internal.telephony.metrics.DataStallRecoveryStats; 53 import com.android.internal.telephony.metrics.TelephonyMetrics; 54 import com.android.telephony.Rlog; 55 56 import java.io.FileDescriptor; 57 import java.io.PrintWriter; 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.util.Arrays; 61 import java.util.Set; 62 import java.util.concurrent.Executor; 63 64 /** 65 * DataStallRecoveryManager monitors the network validation result from connectivity service and 66 * takes actions to recovery data network. 67 */ 68 public class DataStallRecoveryManager extends Handler { 69 private static final boolean VDBG = false; 70 71 /** Recovery actions taken in case of data stall */ 72 @IntDef( 73 value = { 74 RECOVERY_ACTION_GET_DATA_CALL_LIST, 75 RECOVERY_ACTION_CLEANUP, 76 RECOVERY_ACTION_RADIO_RESTART, 77 RECOVERY_ACTION_RESET_MODEM 78 }) 79 @Retention(RetentionPolicy.SOURCE) 80 public @interface RecoveryAction {} 81 82 /* DataStallRecoveryManager queries RIL for link properties (IP addresses, DNS server addresses 83 * etc) using RIL_REQUEST_GET_DATA_CALL_LIST. This will help in cases where the data stall 84 * occurred because of a link property changed but not notified to connectivity service. 85 */ 86 public static final int RECOVERY_ACTION_GET_DATA_CALL_LIST = 0; 87 88 /* DataStallRecoveryManager will request DataNetworkController to reestablish internet using 89 * RIL_REQUEST_DEACTIVATE_DATA_CALL and sets up the data call back using SETUP_DATA_CALL. 90 * It will help to reestablish the channel between RIL and modem. 91 */ 92 public static final int RECOVERY_ACTION_CLEANUP = 1; 93 94 /* DataStallRecoveryManager will request ServiceStateTracker to send RIL_REQUEST_RADIO_POWER 95 * to restart radio. It will restart the radio and re-attch to the network. 96 */ 97 public static final int RECOVERY_ACTION_RADIO_RESTART = 3; 98 99 /* DataStallRecoveryManager will request to reboot modem using NV_RESET_CONFIG. It will recover 100 * if there is a problem in modem side. 101 */ 102 public static final int RECOVERY_ACTION_RESET_MODEM = 4; 103 104 /** Recovered reason taken in case of data stall recovered */ 105 @IntDef( 106 value = { 107 RECOVERED_REASON_NONE, 108 RECOVERED_REASON_DSRM, 109 RECOVERED_REASON_MODEM, 110 RECOVERED_REASON_USER 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface RecoveredReason {} 114 115 // The reason when data stall recovered. 116 /** The data stall not recovered yet. */ 117 private static final int RECOVERED_REASON_NONE = 0; 118 /** The data stall recovered by our DataStallRecoveryManager. */ 119 private static final int RECOVERED_REASON_DSRM = 1; 120 /** The data stall recovered by modem(Radio Power off/on). */ 121 private static final int RECOVERED_REASON_MODEM = 2; 122 /** The data stall recovered by user (Mobile Data Power off/on). */ 123 private static final int RECOVERED_REASON_USER = 3; 124 125 /** The number of milliseconds to wait for the DSRM prediction to complete. */ 126 private static final int DSRM_PREDICT_WAITING_MILLIS = 1000; 127 128 /** Event for send data stall broadcast. */ 129 private static final int EVENT_SEND_DATA_STALL_BROADCAST = 1; 130 131 /** Event for triggering recovery action. */ 132 private static final int EVENT_DO_RECOVERY = 2; 133 134 /** Event for radio state changed. */ 135 private static final int EVENT_RADIO_STATE_CHANGED = 3; 136 137 /** Event for recovery actions changed. */ 138 private static final int EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED = 4; 139 140 /** Event for duration milliseconds changed. */ 141 private static final int EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED = 5; 142 143 @NonNull 144 private final Phone mPhone; 145 @NonNull 146 private final String mLogTag; 147 @NonNull 148 private final LocalLog mLocalLog = new LocalLog(128); 149 @NonNull 150 private final FeatureFlags mFeatureFlags; 151 152 /** Data network controller */ 153 @NonNull 154 private final DataNetworkController mDataNetworkController; 155 156 /** Data config manager */ 157 @NonNull 158 private final DataConfigManager mDataConfigManager; 159 160 /** Cellular data service */ 161 @NonNull 162 private final DataServiceManager mWwanDataServiceManager; 163 164 /** The data stall recovery action. */ 165 @RecoveryAction 166 private int mRecoveryAction; 167 /** The elapsed real time of last recovery attempted */ 168 @ElapsedRealtimeLong 169 private long mTimeLastRecoveryStartMs; 170 /** Whether current network is good or not */ 171 private boolean mIsValidNetwork; 172 /** Whether data stall recovery is triggered or not */ 173 private boolean mRecoveryTriggered = false; 174 /** Whether data stall happened or not. */ 175 private boolean mDataStalled; 176 /** Whether the result of last action(RADIO_RESTART) reported. */ 177 private boolean mLastActionReported; 178 /** The real time for data stall start. */ 179 @VisibleForTesting 180 @ElapsedRealtimeLong 181 public long mDataStallStartMs; 182 /** Last data stall recovery action. */ 183 @RecoveryAction 184 private int mLastAction; 185 /** Last radio power state. */ 186 @RadioPowerState 187 private int mRadioPowerState; 188 /** Whether the NetworkCheckTimer start. */ 189 private boolean mNetworkCheckTimerStarted = false; 190 /** Whether radio state changed during data stall. */ 191 private boolean mRadioStateChangedDuringDataStall; 192 /** Whether airplane mode enabled during data stall. */ 193 private boolean mIsAirPlaneModeEnableDuringDataStall; 194 /** Whether mobile data change to Enabled during data stall. */ 195 private boolean mMobileDataChangedToEnabledDuringDataStall; 196 /** Whether attempted all recovery steps. */ 197 private boolean mIsAttemptedAllSteps; 198 /** Whether internet network that require validation is connected. */ 199 private boolean mIsInternetNetworkConnected; 200 /** The durations for current recovery action */ 201 @ElapsedRealtimeLong 202 private long mTimeElapsedOfCurrentAction; 203 /** Tracks the total number of validation duration a data stall */ 204 private int mValidationCount; 205 /** Tracks the number of validation for current action during a data stall */ 206 private int mActionValidationCount; 207 /** The array for the timers between recovery actions. */ 208 @NonNull 209 private long[] mDataStallRecoveryDelayMillisArray; 210 /** The boolean array for the flags. They are used to skip the recovery actions if needed. */ 211 @NonNull 212 private boolean[] mSkipRecoveryActionArray; 213 214 /** 215 * The content URI for the DSRM recovery actions. 216 * 217 * @see Settings.Global#DSRM_ENABLED_ACTIONS 218 */ 219 private static final Uri CONTENT_URL_DSRM_ENABLED_ACTIONS = Settings.Global.getUriFor( 220 Settings.Global.DSRM_ENABLED_ACTIONS); 221 222 /** 223 * The content URI for the DSRM duration milliseconds. 224 * 225 * @see Settings.Global#DSRM_DURATION_MILLIS 226 */ 227 private static final Uri CONTENT_URL_DSRM_DURATION_MILLIS = Settings.Global.getUriFor( 228 Settings.Global.DSRM_DURATION_MILLIS); 229 230 231 private final DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback; 232 233 private final DataStallRecoveryStats mStats; 234 235 /** The number of milliseconds to wait for the DSRM prediction to complete. */ 236 @ElapsedRealtimeLong 237 private long mPredictWaitingMillis = 0L; 238 239 /** 240 * The data stall recovery manager callback. Note this is only used for passing information 241 * internally in the data stack, should not be used externally. 242 */ 243 public abstract static class DataStallRecoveryManagerCallback extends DataCallback { 244 /** 245 * Constructor 246 * 247 * @param executor The executor of the callback. 248 */ DataStallRecoveryManagerCallback(@onNull @allbackExecutor Executor executor)249 public DataStallRecoveryManagerCallback(@NonNull @CallbackExecutor Executor executor) { 250 super(executor); 251 } 252 253 /** 254 * Called when data stall occurs and needed to tear down / setup a new data network for 255 * internet. 256 */ onDataStallReestablishInternet()257 public abstract void onDataStallReestablishInternet(); 258 } 259 260 /** 261 * Constructor 262 * 263 * @param phone The phone instance. 264 * @param dataNetworkController Data network controller 265 * @param dataServiceManager The WWAN data service manager. 266 * @param featureFlags The feature flag. 267 * @param looper The looper to be used by the handler. Currently the handler thread is the phone 268 * process's main thread. 269 * @param callback Callback to notify data network controller for data stall events. 270 */ DataStallRecoveryManager( @onNull Phone phone, @NonNull DataNetworkController dataNetworkController, @NonNull DataServiceManager dataServiceManager, @NonNull FeatureFlags featureFlags, @NonNull Looper looper, @NonNull DataStallRecoveryManagerCallback callback)271 public DataStallRecoveryManager( 272 @NonNull Phone phone, 273 @NonNull DataNetworkController dataNetworkController, 274 @NonNull DataServiceManager dataServiceManager, 275 @NonNull FeatureFlags featureFlags, 276 @NonNull Looper looper, 277 @NonNull DataStallRecoveryManagerCallback callback) { 278 super(looper); 279 mPhone = phone; 280 mLogTag = "DSRM-" + mPhone.getPhoneId(); 281 log("DataStallRecoveryManager created."); 282 mDataNetworkController = dataNetworkController; 283 mWwanDataServiceManager = dataServiceManager; 284 mFeatureFlags = featureFlags; 285 mDataConfigManager = mDataNetworkController.getDataConfigManager(); 286 mDataNetworkController 287 .getDataSettingsManager() 288 .registerCallback( 289 new DataSettingsManagerCallback(this::post) { 290 @Override 291 public void onDataEnabledChanged( 292 boolean enabled, 293 @TelephonyManager.DataEnabledChangedReason int reason, 294 @NonNull String callingPackage) { 295 onMobileDataEnabledChanged(enabled); 296 } 297 }); 298 mDataStallRecoveryManagerCallback = callback; 299 mRadioPowerState = mPhone.getRadioPowerState(); 300 updateDataStallRecoveryConfigs(); 301 302 registerAllEvents(); 303 304 mStats = new DataStallRecoveryStats(mPhone, mFeatureFlags, dataNetworkController); 305 } 306 307 /** Register for all events that data stall monitor is interested. */ registerAllEvents()308 private void registerAllEvents() { 309 mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) { 310 @Override 311 public void onCarrierConfigChanged() { 312 DataStallRecoveryManager.this.onCarrierConfigUpdated(); 313 } 314 }); 315 mDataNetworkController.registerDataNetworkControllerCallback( 316 new DataNetworkControllerCallback(this::post) { 317 @Override 318 public void onInternetDataNetworkValidationStatusChanged( 319 @ValidationStatus int validationStatus) { 320 onInternetValidationStatusChanged(validationStatus); 321 } 322 323 @Override 324 public void onConnectedInternetDataNetworksChanged( 325 @NonNull Set<DataNetwork> internetNetworks) { 326 boolean anyInternetRequireValidatedConnected = internetNetworks.stream() 327 .anyMatch(nw -> { 328 NetworkCapabilities capabilities = nw.getNetworkCapabilities(); 329 // Only track the networks that require validation. 330 // The criteria is base on NetworkMonitorUtils.java. 331 return capabilities.hasCapability( 332 NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 333 && capabilities.hasCapability( 334 NetworkCapabilities.NET_CAPABILITY_TRUSTED) 335 && capabilities.hasCapability( 336 NetworkCapabilities.NET_CAPABILITY_NOT_VPN); 337 }); 338 if (mIsInternetNetworkConnected != anyInternetRequireValidatedConnected) { 339 mIsInternetNetworkConnected = anyInternetRequireValidatedConnected; 340 logl(mIsInternetNetworkConnected 341 ? "At Least One InternetDataNetwork Connected" 342 : "All InternetDataNetwork Disconnected"); 343 } 344 } 345 }); 346 mPhone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); 347 348 // Register for DSRM global setting changes. 349 mPhone.getContext().getContentResolver().registerContentObserver( 350 CONTENT_URL_DSRM_ENABLED_ACTIONS, false, mContentObserver); 351 mPhone.getContext().getContentResolver().registerContentObserver( 352 CONTENT_URL_DSRM_DURATION_MILLIS, false, mContentObserver); 353 354 } 355 356 @Override handleMessage(Message msg)357 public void handleMessage(Message msg) { 358 logv("handleMessage = " + msg); 359 switch (msg.what) { 360 case EVENT_SEND_DATA_STALL_BROADCAST: 361 mRecoveryTriggered = true; 362 // Verify that DSRM needs to process the recovery action 363 if (!isRecoveryNeeded(false)) { 364 cancelNetworkCheckTimer(); 365 startNetworkCheckTimer(getRecoveryAction()); 366 return; 367 } 368 // Broadcast intent that data stall has been detected. 369 broadcastDataStallDetected(getRecoveryAction()); 370 // Schedule the message to to wait for the predicted value. 371 sendMessageDelayed( 372 obtainMessage(EVENT_DO_RECOVERY), mPredictWaitingMillis); 373 break; 374 case EVENT_DO_RECOVERY: 375 doRecovery(); 376 break; 377 case EVENT_RADIO_STATE_CHANGED: 378 mRadioPowerState = mPhone.getRadioPowerState(); 379 if (mDataStalled) { 380 // Store the radio state changed flag only when data stall occurred. 381 mRadioStateChangedDuringDataStall = true; 382 if (Settings.Global.getInt( 383 mPhone.getContext().getContentResolver(), 384 Settings.Global.AIRPLANE_MODE_ON, 385 0) != 0) { 386 mIsAirPlaneModeEnableDuringDataStall = true; 387 } 388 setRecoveryAction(mLastAction); 389 } 390 break; 391 case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED: 392 updateGlobalConfigActions(); 393 break; 394 case EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED: 395 updateGlobalConfigDurations(); 396 break; 397 default: 398 loge("Unexpected message = " + msg); 399 break; 400 } 401 } 402 403 private final ContentObserver mContentObserver = new ContentObserver(this) { 404 @Override 405 public void onChange(boolean selfChange, Uri uri) { 406 super.onChange(selfChange, uri); 407 if (CONTENT_URL_DSRM_ENABLED_ACTIONS.equals(uri)) { 408 log("onChange URI: " + uri); 409 sendMessage(obtainMessage(EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED)); 410 } else if (CONTENT_URL_DSRM_DURATION_MILLIS.equals(uri)) { 411 log("onChange URI: " + uri); 412 sendMessage(obtainMessage(EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED)); 413 } 414 } 415 }; 416 417 418 @VisibleForTesting getContentObserver()419 public ContentObserver getContentObserver() { 420 return mContentObserver; 421 } 422 423 /** 424 * Updates the skip recovery action array based on DSRM global configuration actions. 425 * 426 * @see Settings.Global#DSRM_ENABLED_ACTIONS 427 */ updateGlobalConfigActions()428 private void updateGlobalConfigActions() { 429 String enabledActions = Settings.Global.getString( 430 mPhone.getContext().getContentResolver(), 431 Settings.Global.DSRM_ENABLED_ACTIONS); 432 433 if (!TextUtils.isEmpty(enabledActions)) { 434 String[] splitEnabledActions = enabledActions.split(","); 435 boolean[] enabledActionsArray = new boolean[splitEnabledActions.length]; 436 for (int i = 0; i < enabledActionsArray.length; i++) { 437 enabledActionsArray[i] = Boolean.parseBoolean(splitEnabledActions[i].trim()); 438 } 439 440 int minLength = Math.min(enabledActionsArray.length, 441 mSkipRecoveryActionArray.length); 442 443 // Update the skip recovery action array. 444 for (int i = 0; i < minLength; i++) { 445 mSkipRecoveryActionArray[i] = !enabledActionsArray[i]; 446 } 447 log("SkipRecoveryAction: " 448 + Arrays.toString(mSkipRecoveryActionArray)); 449 mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS; 450 } else { 451 mPredictWaitingMillis = 0; 452 log("Enabled actions is null"); 453 } 454 } 455 456 /** 457 * Updates the duration millis array based on DSRM global configuration durations. 458 * 459 * @see Settings.Global#DSRM_DURATION_MILLIS 460 */ updateGlobalConfigDurations()461 private void updateGlobalConfigDurations() { 462 String durationMillis = Settings.Global.getString( 463 mPhone.getContext().getContentResolver(), 464 Settings.Global.DSRM_DURATION_MILLIS); 465 466 if (!TextUtils.isEmpty(durationMillis)) { 467 String[] splitDurationMillis = durationMillis.split(","); 468 long[] durationMillisArray = new long[splitDurationMillis.length]; 469 for (int i = 0; i < durationMillisArray.length; i++) { 470 try { 471 durationMillisArray[i] = Long.parseLong(splitDurationMillis[i].trim()); 472 } catch (NumberFormatException e) { 473 mPredictWaitingMillis = 0; 474 loge("Parsing duration millis error"); 475 return; 476 } 477 } 478 479 int minLength = Math.min(durationMillisArray.length, 480 mDataStallRecoveryDelayMillisArray.length); 481 482 // Copy the values from the durationMillisArray array to the 483 // mDataStallRecoveryDelayMillisArray array. 484 System.arraycopy(durationMillisArray, 0, mDataStallRecoveryDelayMillisArray, 485 0, minLength); 486 log("DataStallRecoveryDelayMillis: " 487 + Arrays.toString(mDataStallRecoveryDelayMillisArray)); 488 mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS; 489 } else { 490 mPredictWaitingMillis = 0; 491 log("Duration millis is null"); 492 } 493 } 494 495 /** Update the data stall recovery configs from DataConfigManager. */ updateDataStallRecoveryConfigs()496 private void updateDataStallRecoveryConfigs() { 497 mDataStallRecoveryDelayMillisArray = mDataConfigManager.getDataStallRecoveryDelayMillis(); 498 mSkipRecoveryActionArray = mDataConfigManager.getDataStallRecoveryShouldSkipArray(); 499 500 //Update Global settings 501 updateGlobalConfigActions(); 502 updateGlobalConfigDurations(); 503 } 504 505 /** 506 * Get the duration for specific data stall recovery action. 507 * 508 * @param recoveryAction The recovery action to query. 509 * @return the delay in milliseconds for the specific recovery action. 510 */ 511 @VisibleForTesting getDataStallRecoveryDelayMillis(@ecoveryAction int recoveryAction)512 public long getDataStallRecoveryDelayMillis(@RecoveryAction int recoveryAction) { 513 return mDataStallRecoveryDelayMillisArray[recoveryAction]; 514 } 515 516 /** 517 * Check if the recovery action needs to be skipped. 518 * 519 * @param recoveryAction The recovery action. 520 * @return {@code true} if the action needs to be skipped. 521 */ 522 @VisibleForTesting shouldSkipRecoveryAction(@ecoveryAction int recoveryAction)523 public boolean shouldSkipRecoveryAction(@RecoveryAction int recoveryAction) { 524 return mSkipRecoveryActionArray[recoveryAction]; 525 } 526 527 /** Called when carrier config was updated. */ onCarrierConfigUpdated()528 private void onCarrierConfigUpdated() { 529 updateDataStallRecoveryConfigs(); 530 } 531 532 /** 533 * Called when mobile data setting changed. 534 * 535 * @param enabled true for mobile data settings enabled & false for disabled. 536 */ onMobileDataEnabledChanged(boolean enabled)537 private void onMobileDataEnabledChanged(boolean enabled) { 538 logl("onMobileDataEnabledChanged: DataEnabled:" + enabled + ",DataStalled:" + mDataStalled); 539 // Store the mobile data changed flag (from disabled to enabled) as TRUE 540 // during data stalled. 541 if (mDataStalled && enabled) { 542 mMobileDataChangedToEnabledDuringDataStall = true; 543 setRecoveryAction(mLastAction); 544 } 545 } 546 547 /** 548 * Called when internet validation status passed. We will initialize all parameters. 549 */ reset()550 private void reset() { 551 mIsValidNetwork = true; 552 mRecoveryTriggered = false; 553 mIsAttemptedAllSteps = false; 554 mRadioStateChangedDuringDataStall = false; 555 mIsAirPlaneModeEnableDuringDataStall = false; 556 mMobileDataChangedToEnabledDuringDataStall = false; 557 cancelNetworkCheckTimer(); 558 mTimeLastRecoveryStartMs = 0; 559 mLastAction = RECOVERY_ACTION_GET_DATA_CALL_LIST; 560 mRecoveryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST; 561 mValidationCount = 0; 562 mActionValidationCount = 0; 563 } 564 565 /** 566 * Called when internet validation status changed. 567 * 568 * @param status Validation status. 569 */ onInternetValidationStatusChanged(@alidationStatus int status)570 private void onInternetValidationStatusChanged(@ValidationStatus int status) { 571 logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status)); 572 final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID; 573 if (mFeatureFlags.dsrsDiagnosticsEnabled()) { 574 mValidationCount += 1; 575 mActionValidationCount += 1; 576 } 577 setNetworkValidationState(isValid); 578 if (isValid) { 579 if (mFeatureFlags.dsrsDiagnosticsEnabled()) { 580 // Broadcast intent that data stall recovered. 581 broadcastDataStallDetected(mLastAction); 582 } 583 reset(); 584 } else if (isRecoveryNeeded(true)) { 585 // Set the network as invalid, because recovery is needed 586 mIsValidNetwork = false; 587 log("trigger data stall recovery"); 588 mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime(); 589 sendMessage(obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST)); 590 } 591 } 592 593 /** Reset the action to initial step. */ resetAction()594 private void resetAction() { 595 mTimeLastRecoveryStartMs = 0; 596 mMobileDataChangedToEnabledDuringDataStall = false; 597 mRadioStateChangedDuringDataStall = false; 598 mIsAirPlaneModeEnableDuringDataStall = false; 599 setRecoveryAction(RECOVERY_ACTION_GET_DATA_CALL_LIST); 600 } 601 602 /** 603 * Get recovery action from settings. 604 * 605 * @return recovery action 606 */ 607 @VisibleForTesting 608 @RecoveryAction getRecoveryAction()609 public int getRecoveryAction() { 610 log("getRecoveryAction: " + recoveryActionToString(mRecoveryAction)); 611 return mRecoveryAction; 612 } 613 614 /** 615 * Put recovery action into settings. 616 * 617 * @param action The next recovery action. 618 */ 619 @VisibleForTesting setRecoveryAction(@ecoveryAction int action)620 public void setRecoveryAction(@RecoveryAction int action) { 621 // Reset the validation count for action change 622 if (mFeatureFlags.dsrsDiagnosticsEnabled() && mRecoveryAction != action) { 623 mActionValidationCount = 0; 624 } 625 mRecoveryAction = action; 626 627 // Check if the mobile data enabled is TRUE, it means that the mobile data setting changed 628 // from DISABLED to ENABLED, we will set the next recovery action to 629 // RECOVERY_ACTION_RADIO_RESTART due to already did the RECOVERY_ACTION_CLEANUP. 630 if (mMobileDataChangedToEnabledDuringDataStall 631 && mRecoveryAction < RECOVERY_ACTION_RADIO_RESTART) { 632 mRecoveryAction = RECOVERY_ACTION_RADIO_RESTART; 633 } 634 // Check if the radio state changed from off to on, it means that the modem already 635 // did the radio restart, we will set the next action to RECOVERY_ACTION_RESET_MODEM. 636 if (mRadioStateChangedDuringDataStall 637 && mRadioPowerState == TelephonyManager.RADIO_POWER_ON) { 638 mRecoveryAction = RECOVERY_ACTION_RESET_MODEM; 639 } 640 // To check the flag from DataConfigManager if we need to skip the step. 641 if (shouldSkipRecoveryAction(mRecoveryAction)) { 642 switch (mRecoveryAction) { 643 case RECOVERY_ACTION_GET_DATA_CALL_LIST: 644 setRecoveryAction(RECOVERY_ACTION_CLEANUP); 645 break; 646 case RECOVERY_ACTION_CLEANUP: 647 setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART); 648 break; 649 case RECOVERY_ACTION_RADIO_RESTART: 650 setRecoveryAction(RECOVERY_ACTION_RESET_MODEM); 651 break; 652 case RECOVERY_ACTION_RESET_MODEM: 653 resetAction(); 654 break; 655 } 656 } 657 658 log("setRecoveryAction: " + recoveryActionToString(mRecoveryAction)); 659 } 660 661 /** 662 * Check if recovery already started. 663 * 664 * @return {@code true} if recovery already started, {@code false} recovery not started. 665 */ isRecoveryAlreadyStarted()666 private boolean isRecoveryAlreadyStarted() { 667 return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST || mRecoveryTriggered; 668 } 669 670 /** 671 * Get elapsed time since last recovery. 672 * 673 * @return the time since last recovery started. 674 */ getElapsedTimeSinceRecoveryMs()675 private long getElapsedTimeSinceRecoveryMs() { 676 return (SystemClock.elapsedRealtime() - mTimeLastRecoveryStartMs); 677 } 678 679 /** 680 * Get duration time for current recovery action. 681 * 682 * @return the time duration for current recovery action. 683 */ getDurationOfCurrentRecoveryMs()684 private long getDurationOfCurrentRecoveryMs() { 685 return (SystemClock.elapsedRealtime() - mTimeElapsedOfCurrentAction); 686 } 687 688 /** 689 * Broadcast intent when data stall occurred. 690 * 691 * @param recoveryAction Send the data stall detected intent with RecoveryAction info. 692 */ broadcastDataStallDetected(@ecoveryAction int recoveryAction)693 private void broadcastDataStallDetected(@RecoveryAction int recoveryAction) { 694 log("broadcastDataStallDetected recoveryAction: " + recoveryAction); 695 Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED); 696 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); 697 intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction); 698 699 // Get the information for DSRS state 700 final boolean isRecovered = !mDataStalled; 701 final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs); 702 @RecoveredReason final int reason = getRecoveredReason(mIsValidNetwork); 703 final int durationOfAction = (int) getDurationOfCurrentRecoveryMs(); 704 if (mFeatureFlags.dsrsDiagnosticsEnabled()) { 705 log("mValidationCount=" + mValidationCount 706 + ", mActionValidationCount=" + mActionValidationCount); 707 } 708 709 // Get the bundled DSRS stats. 710 Bundle bundle = mStats.getDataStallRecoveryMetricsData( 711 recoveryAction, isRecovered, duration, reason, mValidationCount, 712 mActionValidationCount, durationOfAction); 713 714 // Put the bundled stats extras on the intent. 715 intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle); 716 717 mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE); 718 } 719 720 /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */ getDataCallList()721 private void getDataCallList() { 722 log("getDataCallList: request data call list"); 723 mWwanDataServiceManager.requestDataCallList(null); 724 } 725 726 /** Recovery Action: RECOVERY_ACTION_CLEANUP */ cleanUpDataNetwork()727 private void cleanUpDataNetwork() { 728 log("cleanUpDataNetwork: notify clean up data network"); 729 mDataStallRecoveryManagerCallback.invokeFromExecutor( 730 mDataStallRecoveryManagerCallback::onDataStallReestablishInternet); 731 } 732 733 /** Recovery Action: RECOVERY_ACTION_RADIO_RESTART */ powerOffRadio()734 private void powerOffRadio() { 735 log("powerOffRadio: Restart radio"); 736 mPhone.getServiceStateTracker().powerOffRadioSafely(); 737 } 738 739 /** Recovery Action: RECOVERY_ACTION_RESET_MODEM */ rebootModem()740 private void rebootModem() { 741 log("rebootModem: reboot modem"); 742 mPhone.rebootModem(null); 743 } 744 745 /** 746 * Initialize the network check timer. 747 * 748 * @param action The recovery action to start the network check timer. 749 */ startNetworkCheckTimer(@ecoveryAction int action)750 private void startNetworkCheckTimer(@RecoveryAction int action) { 751 // Ignore send message delayed due to reached the last action. 752 if (action == RECOVERY_ACTION_RESET_MODEM) return; 753 log("startNetworkCheckTimer(): " + getDataStallRecoveryDelayMillis(action) + "ms"); 754 if (!mNetworkCheckTimerStarted) { 755 mNetworkCheckTimerStarted = true; 756 mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime(); 757 sendMessageDelayed( 758 obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST), 759 getDataStallRecoveryDelayMillis(action)); 760 } 761 } 762 763 /** Cancel the network check timer. */ cancelNetworkCheckTimer()764 private void cancelNetworkCheckTimer() { 765 log("cancelNetworkCheckTimer()"); 766 if (mNetworkCheckTimerStarted) { 767 mNetworkCheckTimerStarted = false; 768 removeMessages(EVENT_SEND_DATA_STALL_BROADCAST); 769 } 770 } 771 772 /** 773 * Check the conditions if we need to do recovery action. 774 * 775 * @param isNeedToCheckTimer {@code true} indicating we need the check timer when 776 * we receive the internet validation status changed. 777 * @return {@code true} if need to do recovery action, {@code false} no need to do recovery 778 * action. 779 */ isRecoveryNeeded(boolean isNeedToCheckTimer)780 private boolean isRecoveryNeeded(boolean isNeedToCheckTimer) { 781 logv("enter: isRecoveryNeeded()"); 782 783 // Skip if network is invalid and recovery was not started yet 784 if (!mIsValidNetwork && !isRecoveryAlreadyStarted()) { 785 logl("skip when network still remains invalid and recovery was not started yet"); 786 return false; 787 } 788 789 // Skip recovery if we have already attempted all steps. 790 if (mIsAttemptedAllSteps) { 791 logl("skip retrying continue recovery action"); 792 return false; 793 } 794 795 // To avoid back to back recovery, wait for a grace period 796 if (getElapsedTimeSinceRecoveryMs() < getDataStallRecoveryDelayMillis(mLastAction) 797 && isNeedToCheckTimer) { 798 logl("skip back to back data stall recovery"); 799 return false; 800 } 801 802 // Skip recovery if it can cause a call to drop 803 if (mPhone.getState() != PhoneConstants.State.IDLE 804 && getRecoveryAction() > RECOVERY_ACTION_CLEANUP) { 805 logl("skip data stall recovery as there is an active call"); 806 return false; 807 } 808 809 // Skip when poor signal strength 810 if (mPhone.getSignalStrength().getLevel() <= CellSignalStrength.SIGNAL_STRENGTH_POOR) { 811 logl("skip data stall recovery as in poor signal condition"); 812 return false; 813 } 814 815 if (!mDataNetworkController.isInternetDataAllowed(true/* ignoreExistingNetworks */)) { 816 logl("skip data stall recovery as data not allowed."); 817 return false; 818 } 819 820 if (!mIsInternetNetworkConnected) { 821 logl("skip data stall recovery as data not connected"); 822 return false; 823 } 824 return true; 825 } 826 827 /** 828 * Set the validation status into metrics. 829 * 830 * @param isValid true for validation passed & false for validation failed 831 */ setNetworkValidationState(boolean isValid)832 private void setNetworkValidationState(boolean isValid) { 833 boolean isLogNeeded = false; 834 int timeDuration = 0; 835 int timeDurationOfCurrentAction; 836 boolean isFirstDataStall = false; 837 boolean isFirstValidationAfterDoRecovery = false; 838 @RecoveredReason int reason = getRecoveredReason(isValid); 839 // Validation status is true and was not data stall. 840 if (isValid && !mDataStalled) { 841 return; 842 } 843 844 if (!mDataStalled) { 845 // First data stall 846 isLogNeeded = true; 847 mDataStalled = true; 848 isFirstDataStall = true; 849 mDataStallStartMs = SystemClock.elapsedRealtime(); 850 logl("data stall: start time = " + DataUtils.elapsedTimeToString(mDataStallStartMs)); 851 } else if (!mLastActionReported) { 852 // When the first validation status appears, enter this block. 853 isLogNeeded = true; 854 timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs); 855 mLastActionReported = true; 856 isFirstValidationAfterDoRecovery = true; 857 } 858 859 if (isValid) { 860 // When the validation passed(mobile data resume), enter this block. 861 isLogNeeded = true; 862 timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs); 863 mLastActionReported = false; 864 mDataStalled = false; 865 } 866 867 if (isLogNeeded) { 868 timeDurationOfCurrentAction = 869 ((getRecoveryAction() > RECOVERY_ACTION_GET_DATA_CALL_LIST 870 && !mIsAttemptedAllSteps) 871 || mLastAction == RECOVERY_ACTION_RESET_MODEM) 872 ? (int) getDurationOfCurrentRecoveryMs() : 0; 873 874 mStats.uploadMetrics( 875 mLastAction, isValid, timeDuration, reason, 876 isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction); 877 logl( 878 "data stall: " 879 + (isFirstDataStall ? "start" : !isValid ? "in process" : "end") 880 + ", lastaction=" 881 + recoveryActionToString(mLastAction) 882 + ", isRecovered=" 883 + isValid 884 + ", reason=" 885 + recoveredReasonToString(reason) 886 + ", isFirstValidationAfterDoRecovery=" 887 + isFirstValidationAfterDoRecovery 888 + ", TimeDuration=" 889 + timeDuration 890 + ", TimeDurationForCurrentRecoveryAction=" 891 + timeDurationOfCurrentAction); 892 } 893 } 894 895 /** 896 * Get the data stall recovered reason. 897 * 898 * @param isValid true for validation passed & false for validation failed 899 */ 900 @RecoveredReason getRecoveredReason(boolean isValid)901 private int getRecoveredReason(boolean isValid) { 902 if (!isValid) return RECOVERED_REASON_NONE; 903 904 int ret = RECOVERED_REASON_DSRM; 905 if (mRadioStateChangedDuringDataStall) { 906 if (mLastAction <= RECOVERY_ACTION_CLEANUP) { 907 ret = RECOVERED_REASON_MODEM; 908 } 909 if (mIsAirPlaneModeEnableDuringDataStall) { 910 ret = RECOVERED_REASON_USER; 911 } 912 } else if (mMobileDataChangedToEnabledDuringDataStall) { 913 ret = RECOVERED_REASON_USER; 914 } 915 return ret; 916 } 917 918 /** Perform a series of data stall recovery actions. */ doRecovery()919 private void doRecovery() { 920 @RecoveryAction final int recoveryAction = getRecoveryAction(); 921 final int signalStrength = mPhone.getSignalStrength().getLevel(); 922 923 TelephonyMetrics.getInstance() 924 .writeSignalStrengthEvent(mPhone.getPhoneId(), signalStrength); 925 TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction); 926 mLastAction = recoveryAction; 927 mLastActionReported = false; 928 mNetworkCheckTimerStarted = false; 929 mTimeElapsedOfCurrentAction = SystemClock.elapsedRealtime(); 930 931 switch (recoveryAction) { 932 case RECOVERY_ACTION_GET_DATA_CALL_LIST: 933 logl("doRecovery(): get data call list"); 934 getDataCallList(); 935 setRecoveryAction(RECOVERY_ACTION_CLEANUP); 936 break; 937 case RECOVERY_ACTION_CLEANUP: 938 logl("doRecovery(): cleanup all connections"); 939 cleanUpDataNetwork(); 940 setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART); 941 break; 942 case RECOVERY_ACTION_RADIO_RESTART: 943 logl("doRecovery(): restarting radio"); 944 setRecoveryAction(RECOVERY_ACTION_RESET_MODEM); 945 powerOffRadio(); 946 break; 947 case RECOVERY_ACTION_RESET_MODEM: 948 logl("doRecovery(): modem reset"); 949 rebootModem(); 950 resetAction(); 951 mIsAttemptedAllSteps = true; 952 break; 953 default: 954 throw new RuntimeException( 955 "doRecovery: Invalid recoveryAction = " 956 + recoveryActionToString(recoveryAction)); 957 } 958 959 startNetworkCheckTimer(mLastAction); 960 } 961 962 /** 963 * Convert @RecoveredReason to string 964 * 965 * @param reason The recovered reason. 966 * @return The recovered reason in string format. 967 */ 968 @NonNull recoveredReasonToString(@ecoveredReason int reason)969 private static String recoveredReasonToString(@RecoveredReason int reason) { 970 return switch (reason) { 971 case RECOVERED_REASON_NONE -> "RECOVERED_REASON_NONE"; 972 case RECOVERED_REASON_DSRM -> "RECOVERED_REASON_DSRM"; 973 case RECOVERED_REASON_MODEM -> "RECOVERED_REASON_MODEM"; 974 case RECOVERED_REASON_USER -> "RECOVERED_REASON_USER"; 975 default -> "Unknown(" + reason + ")"; 976 }; 977 } 978 979 /** 980 * Convert RadioPowerState to string 981 * 982 * @param state The radio power state 983 * @return The radio power state in string format. 984 */ 985 @NonNull radioPowerStateToString(@adioPowerState int state)986 private static String radioPowerStateToString(@RadioPowerState int state) { 987 return switch (state) { 988 case TelephonyManager.RADIO_POWER_OFF -> "RADIO_POWER_OFF"; 989 case TelephonyManager.RADIO_POWER_ON -> "RADIO_POWER_ON"; 990 case TelephonyManager.RADIO_POWER_UNAVAILABLE -> "RADIO_POWER_UNAVAILABLE"; 991 default -> "Unknown(" + state + ")"; 992 }; 993 } 994 995 /** 996 * Convert RecoveryAction to string 997 * 998 * @param action The recovery action 999 * @return The recovery action in string format. 1000 */ 1001 @NonNull 1002 private static String recoveryActionToString(@RecoveryAction int action) { 1003 return switch (action) { 1004 case RECOVERY_ACTION_GET_DATA_CALL_LIST -> "RECOVERY_ACTION_GET_DATA_CALL_LIST"; 1005 case RECOVERY_ACTION_CLEANUP -> "RECOVERY_ACTION_CLEANUP"; 1006 case RECOVERY_ACTION_RADIO_RESTART -> "RECOVERY_ACTION_RADIO_RESTART"; 1007 case RECOVERY_ACTION_RESET_MODEM -> "RECOVERY_ACTION_RESET_MODEM"; 1008 default -> "Unknown(" + action + ")"; 1009 }; 1010 } 1011 1012 /** 1013 * Log debug messages. 1014 * 1015 * @param s debug messages 1016 */ 1017 private void log(@NonNull String s) { 1018 Rlog.d(mLogTag, s); 1019 } 1020 1021 /** 1022 * Log verbose messages. 1023 * 1024 * @param s debug messages. 1025 */ 1026 private void logv(@NonNull String s) { 1027 if (VDBG) Rlog.v(mLogTag, s); 1028 } 1029 1030 /** 1031 * Log error messages. 1032 * 1033 * @param s error messages 1034 */ 1035 private void loge(@NonNull String s) { 1036 Rlog.e(mLogTag, s); 1037 } 1038 1039 /** 1040 * Log debug messages and also log into the local log. 1041 * 1042 * @param s debug messages 1043 */ 1044 private void logl(@NonNull String s) { 1045 log(s); 1046 mLocalLog.log(s); 1047 } 1048 1049 /** 1050 * Dump the state of DataStallRecoveryManager 1051 * 1052 * @param fd File descriptor 1053 * @param printWriter Print writer 1054 * @param args Arguments 1055 */ 1056 public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 1057 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 1058 pw.println( 1059 DataStallRecoveryManager.class.getSimpleName() + "-" + mPhone.getPhoneId() + ":"); 1060 pw.increaseIndent(); 1061 1062 pw.println("mIsValidNetwork=" + mIsValidNetwork); 1063 pw.println("mIsInternetNetworkConnected=" + mIsInternetNetworkConnected); 1064 pw.println("mIsAirPlaneModeEnableDuringDataStall=" + mIsAirPlaneModeEnableDuringDataStall); 1065 pw.println("mDataStalled=" + mDataStalled); 1066 pw.println("mLastAction=" + recoveryActionToString(mLastAction)); 1067 pw.println("mIsAttemptedAllSteps=" + mIsAttemptedAllSteps); 1068 pw.println("mDataStallStartMs=" + DataUtils.elapsedTimeToString(mDataStallStartMs)); 1069 pw.println("mRadioPowerState=" + radioPowerStateToString(mRadioPowerState)); 1070 pw.println("mLastActionReported=" + mLastActionReported); 1071 pw.println("mTimeLastRecoveryStartMs=" 1072 + DataUtils.elapsedTimeToString(mTimeLastRecoveryStartMs)); 1073 pw.println("getRecoveryAction()=" + recoveryActionToString(getRecoveryAction())); 1074 pw.println("mRadioStateChangedDuringDataStall=" + mRadioStateChangedDuringDataStall); 1075 pw.println( 1076 "mMobileDataChangedToEnabledDuringDataStall=" 1077 + mMobileDataChangedToEnabledDuringDataStall); 1078 pw.println("mPredictWaitingMillis=" + mPredictWaitingMillis); 1079 pw.println( 1080 "DataStallRecoveryDelayMillisArray=" 1081 + Arrays.toString(mDataStallRecoveryDelayMillisArray)); 1082 pw.println("SkipRecoveryActionArray=" + Arrays.toString(mSkipRecoveryActionArray)); 1083 pw.decreaseIndent(); 1084 pw.println(""); 1085 1086 pw.println("Local logs:"); 1087 pw.increaseIndent(); 1088 mLocalLog.dump(fd, pw, args); 1089 pw.decreaseIndent(); 1090 pw.decreaseIndent(); 1091 } 1092 } 1093