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