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