Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 34028715 authored by Willy Hu's avatar Willy Hu Committed by Jack Yu
Browse files

Add Data Stall Recovery Mechanism

- Implement the Data Stall Recovery main logic
- Add the check conditions for skip the recovery when poor signal
- Add the DataStallRecoveryManagerTest file

Bug: 178670629
Test: 1. Build pass.
      2. atest FrameworksTelephonyTests
Merged-In: I92a148e5e065af08883bee1c6d570fb9521bfb24
Change-Id: I92a148e5e065af08883bee1c6d570fb9521bfb24
parent 4a3ecd2e
Loading
Loading
Loading
Loading
+334 −114
Original line number Diff line number Diff line
@@ -17,30 +17,29 @@
package com.android.internal.telephony.data;

import android.annotation.CallbackExecutor;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.os.AsyncTask;
import android.net.NetworkAgent;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.os.SystemClock;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.ValidationStatus;
import android.telephony.PreciseDataConnectionState;
import android.telephony.SignalStrength;
import android.telephony.CellSignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
import com.android.internal.telephony.metrics.DataStallRecoveryStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
@@ -54,6 +53,7 @@ import java.util.concurrent.Executor;
 * takes actions to recovery data network.
 */
public class DataStallRecoveryManager extends Handler {
    private static final boolean VDBG = false;

    /** Recovery actions taken in case of data stall */
    @IntDef(
@@ -65,14 +65,42 @@ public class DataStallRecoveryManager extends Handler {
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RecoveryAction {};

    /* DataStallRecoveryManager queries RIL for link properties (IP addresses, DNS server addresses
     * etc) using RIL_REQUEST_GET_DATA_CALL_LIST.  This will help in cases where the data stall
     * occurred because of a link property changed but not notified to connectivity service.
     */
    private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST = 0;

    /* DataStallRecoveryManager will request DataNetworkController to reestablish internet using
     * RIL_REQUEST_DEACTIVATE_DATA_CALL and sets up the data call back using SETUP_DATA_CALL.
     * It will help to reestablish the channel between RIL and modem.
     */
    private static final int RECOVERY_ACTION_CLEANUP = 1;

    /* DataStallRecoveryManager will request ServiceStateTracker to send RIL_REQUEST_RADIO_POWER
     * to restart radio. It will restart the radio and re-attch to the network.
     */
    private static final int RECOVERY_ACTION_RADIO_RESTART = 2;

    /* DataStallRecoveryManager will request to reboot modem using NV_RESET_CONFIG. It will recover
     * if there is a problem in modem side.
     */
    private static final int RECOVERY_ACTION_RESET_MODEM = 3;

    /** Event for data config updated. */
    private static final int EVENT_DATA_CONFIG_UPDATED = 1;


    /** Event for triggering recovery action. */
    private static final int EVENT_DO_RECOVERY = 2;

    /** Event for mobile data setting changed. */
    private static final int EVENT_MOBILE_DATA_SETTINGS_CHANGED = 3;

    /** Event for radio state changed. */
    private static final int EVENT_RADIO_STATE_CHANGED = 4;

    private final @NonNull Phone mPhone;
    private final @NonNull String mLogTag;
    private final @NonNull LocalLog mLocalLog = new LocalLog(128);
@@ -86,20 +114,24 @@ public class DataStallRecoveryManager extends Handler {
    /** Cellular data service */
    private final @NonNull DataServiceManager mWwanDataServiceManager;

    /** Resolver */
    private ContentResolver mResolver;

    private Network mNetwork;
    private NetworkCallback mNetworkCallback;

    /** Connectivity Manager */
    private ConnectivityManager mConnectivityManager;

    /** Telephony Manager */
    private TelephonyManager mTelephonyManager;

    /** Listening the callback from TelephonyCallback. */
    private TelephonyStateListener mTelephonyStateListener;
    /** The data stall recovery action. */
    private @RecoveryAction int mRecovryAction;
    /** The elapsed real time of last recovery attempted */
    private @ElapsedRealtimeLong long mTimeLastRecoveryStartMs;
    /** Whether current network is good or not */
    private boolean mIsValidNetwork;
    /** Whether data stall happened or not. */
    private boolean mDataStalled;
    /** Whether the result of last action(RADIO_RESTART) reported. */
    private boolean mLastActionReported;
    /** The real time for data stall start. */
    private @ElapsedRealtimeLong long mDataStallStartMs;
    /** Last data stall recovery action. */
    private @RecoveryAction int mLastAction;
    /** Last radio power state. */
    private @RadioPowerState int mRadioPowerState;
    /** Whether the NetworkCheckTimer start. */
    private boolean mNetworkCheckTimerStarted = false;

    private @NonNull DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback;

@@ -130,25 +162,24 @@ public class DataStallRecoveryManager extends Handler {
     * @param phone The phone instance.
     * @param dataNetworkController Data network controller
     * @param dataServiceManager The WWAN data service manager.
     * @param looper The looper to be used by the handler. Currently the handler thread is the
     * phone process's main thread.
     * @param looper The looper to be used by the handler. Currently the handler thread is the phone
     *     process's main thread.
     * @param callback Callback to notify data network controller for data stall events.
     */
    public DataStallRecoveryManager(@NonNull Phone phone,
    public DataStallRecoveryManager(
            @NonNull Phone phone,
            @NonNull DataNetworkController dataNetworkController,
            @NonNull DataServiceManager dataServiceManager, @NonNull Looper looper,
            @NonNull DataServiceManager dataServiceManager,
            @NonNull Looper looper,
            @NonNull DataStallRecoveryManagerCallback callback) {
        super(looper);
        mPhone = phone;
        mLogTag = "DSTMTR-" + mPhone.getPhoneId();
        mLogTag = "DSRM-" + mPhone.getPhoneId();
        mDataNetworkController = dataNetworkController;
        mWwanDataServiceManager = dataServiceManager;
        mDataConfigManager = mDataNetworkController.getDataConfigManager();
        mDataStallRecoveryManagerCallback = callback;
        mResolver = mPhone.getContext().getContentResolver();
        mTelephonyManager = mPhone.getContext().getSystemService(TelephonyManager.class);
        mConnectivityManager = mPhone.getContext().getSystemService(ConnectivityManager.class);
        mTelephonyStateListener = new TelephonyStateListener();
        mRadioPowerState = mPhone.getRadioPowerState();

        registerAllEvents();
    }
@@ -174,42 +205,29 @@ public class DataStallRecoveryManager extends Handler {
                    public void onInternetDataNetworkDisconnected() {
                        // onInternetDataNetworkDisconnected();
                    }
                }, false);
        mTelephonyManager.registerTelephonyCallback(
                    new HandlerExecutor(this), mTelephonyStateListener);
    }

    /** Telephony State Listener. */
    private class TelephonyStateListener extends TelephonyCallback implements
                TelephonyCallback.RadioPowerStateListener,
                TelephonyCallback.PreciseDataConnectionStateListener,
                TelephonyCallback.SignalStrengthsListener {

        @Override
        public void onPreciseDataConnectionStateChanged(
            PreciseDataConnectionState connectionState) {
            // TODO: (b/178670629): Add the logic for PreciseDataConnectionStateChanged.
        }

        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            // TODO: (b/178670629): Add the logic for Signal Strength Changed.
        }

        @Override
        public void onRadioPowerStateChanged(int state) {
            // TODO: (b/178670629): Listen the Radio Power State Changed.
        }
                },
                false);
        mPhone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
    }

    @Override
    public void handleMessage(Message msg) {
        logv("handleMessage = " + msg);
        switch (msg.what) {
            case EVENT_DATA_CONFIG_UPDATED:
                onDataConfigUpdated();
                break;
            case EVENT_DO_RECOVERY:
                doRecovery();
                break;
            case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
                onMobileDataSettingsChanged();
                break;
            case EVENT_RADIO_STATE_CHANGED:
                mRadioPowerState = mPhone.getRadioPowerState();
                break;
            default:
                loge("Unexpected message " + msg);
                loge("Unexpected message = " + msg);
                break;
        }
    }
@@ -224,18 +242,48 @@ public class DataStallRecoveryManager extends Handler {
     *
     * @param validationStatus Validation status.
     */
    private void onInternetValidationStatusChanged(@ValidationStatus int validationStatus) {
        // TODO: (b/178670629): Add the logic when Validation Status Changed.
    private void onInternetValidationStatusChanged(@ValidationStatus int status) {
        logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status));
        final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
        setNetworkValidationState(isValid);
        if (isValid) {
            mIsValidNetwork = true;
            cancelNetworkCheckTimer();
            resetAction();
        } else {
            if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
                mIsValidNetwork = false;
                if (isRecoveryNeeded()) {
                    log("trigger data stall recovery");
                    mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
                    sendMessage(obtainMessage(EVENT_DO_RECOVERY));
                }
            }
        }
    }

    /** Called when mobile data setiings changed. */
    private void onMobileDataSettingsChanged() {
        logv("onMobileDataSettingsChanged");
        // TODO: (b/178670629): Get mobile data settings from DataSettingsManager.
    }

    /** Get recovery action from settings. */
    /** Reset the action to initial step. */
    private void resetAction() {
        mTimeLastRecoveryStartMs = 0;
        setRecoveryAction(RECOVERY_ACTION_GET_DATA_CALL_LIST);
    }

    /**
     * Get recovery action from settings.
     *
     * @return recovery action
     */
    @VisibleForTesting
    @RecoveryAction
    private int getRecoveryAction() {
        @RecoveryAction int action = Settings.System.getInt(mResolver,
                "radio.data.stall.recovery.action", RECOVERY_ACTION_GET_DATA_CALL_LIST);
        log("getRecoveryAction: " + action);
        return action;
    public int getRecoveryAction() {
        log("getRecoveryAction: " + recoveryActionToString(mRecovryAction));
        return mRecovryAction;
    }

    /**
@@ -243,20 +291,38 @@ public class DataStallRecoveryManager extends Handler {
     *
     * @param action The next recovery action.
     */
    private void putRecoveryAction(@RecoveryAction int action) {
        Settings.System.putInt(mResolver, "radio.data.stall.recovery.action", action);
        log("putRecoveryAction: " + action);
    @VisibleForTesting
    public void setRecoveryAction(@RecoveryAction int action) {
        mRecovryAction = action;
        log("setRecoveryAction: " + recoveryActionToString(mRecovryAction));
    }

    /** Check if recovery already started. */
    /**
     * Check if recovery already started.
     *
     * @return {@code true} if recovery already started, {@code false} recovery not started.
     */
    private boolean isRecoveryAlreadyStarted() {
        return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST;
    }

    /** Get duration between recovery from DataStallRecoveryRule. */
    /**
     * Get elapsed time since last recovery.
     *
     * @return the time since last recovery started.
     */
    private long getElapsedTimeSinceRecoveryMs() {
        return (SystemClock.elapsedRealtime() - mTimeLastRecoveryStartMs);
    }

    /**
     * Get duration between recovery from DataStallRecoveryConfig.
     *
     * @return the time in milliseconds between recovery action.
     */
    private long getMinDurationBetweenRecovery() {
        // TODO: (b/178670629): Get the duration from DataConfigManager
        return 0;
        return 3 * 60 * 1000;
    }

    /**
@@ -274,46 +340,178 @@ public class DataStallRecoveryManager extends Handler {

    /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */
    private void getDataCallList() {
        if (mPhone == null) {
            loge("getDataCallList: mPhone is null");
            return;
        }
        log("getDataCallList: request data call list");
        mWwanDataServiceManager.requestDataCallList(null);
    }

    /** Recovery Action: RECOVERY_ACTION_CLEANUP */
    private void cleanUpDataCall(){
        if (mPhone == null) {
            loge("cleanUpDataCall: getDataCallList: mPhone is null");
            return;
        }
        log("cleanUpDataCall: notify clean up data call");
    private void cleanUpDataNetwork() {
        log("cleanUpDataNetwork: notify clean up data network");
        mDataStallRecoveryManagerCallback.invokeFromExecutor(
                () -> mDataStallRecoveryManagerCallback.onDataStallReestablishInternet());
    }

    /** Recovery Action: RECOVERY_ACTION_RADIO_RESTART */
    private void restartRadio(){
        if(mPhone == null) {
            loge("restartRadio: No mPhone found!");
            return;
        }
        log("restartRadio: Restart radio" );
    private void powerOffRadio() {
        log("powerOffRadio: Restart radio");
        mPhone.getServiceStateTracker().powerOffRadioSafely();
    }

    /** Recovery Action: RECOVERY_ACTION_RESET_MODEM */
    private void modemReset(){
        AsyncTask.execute(() -> {
            try{
                log("modemReset: NV reload");
                mTelephonyManager.rebootRadio();
    private void rebootModem() {
        log("rebootModem: reboot modem");
        mPhone.rebootModem(null);
    }

    /** Initialize the network check timer. */
    private void startNetworkCheckTimer() {
        log("startNetworkCheckTimer()");
        if (!mNetworkCheckTimerStarted) {
            mNetworkCheckTimerStarted = true;
            sendMessageDelayed(obtainMessage(EVENT_DO_RECOVERY), getMinDurationBetweenRecovery());
        }
    }

    /** Cancel the network check timer. */
    private void cancelNetworkCheckTimer() {
        log("cancelNetworkCheckTimer()");
        if (mNetworkCheckTimerStarted) {
            mNetworkCheckTimerStarted = false;
            removeMessages(EVENT_DO_RECOVERY);
        }
            catch(Exception e){
                loge("modemReset: Exception: " + e.toString());
    }
        });

    /**
     * Check the conditions if we need to do recovery action.
     *
     * @return {@code true} if need to do recovery action, {@code false} no need to do recovery
     *     action.
     */
    private boolean isRecoveryNeeded() {
        logv("enter: isRecoveryNeeded()");
        // To avoid back to back recovery, wait for a grace period
        if (getElapsedTimeSinceRecoveryMs() < getMinDurationBetweenRecovery()) {
            log("skip back to back data stall recovery");
            return false;
        }

        // Skip recovery if it can cause a call to drop
        if (mPhone.getState() != PhoneConstants.State.IDLE
                && getRecoveryAction() > RECOVERY_ACTION_CLEANUP) {
            log("skip data stall recovery as there is an active call");
            return false;
        }

        // Skip when poor signal strength
        if (mPhone.getSignalStrength().getLevel() <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
            log("skip data stall recovery as in poor signal condition");
            resetAction();
            return false;
        }

        // TODO: (b/178670629): check the customized carrier config to skip the recovery action

        return true;
    }

    /**
     * Set the validation status into metrics.
     *
     * @param isValid true for validation passed & false for validation failed
     */
    private void setNetworkValidationState(boolean isValid) {
        // Validation status is true and was not data stall.
        if (isValid && !mDataStalled) {
            return;
        }

        if (!mDataStalled) {
            mDataStalled = true;
            mDataStallStartMs = SystemClock.elapsedRealtime();
            logl("data stall: start time = " + DataUtils.elapsedTimeToString(mDataStallStartMs));
            return;
        }

        if (!mLastActionReported) {
            int timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
            logl(
                    "data stall: lastaction = "
                            + recoveryActionToString(mLastAction)
                            + ", isRecovered = "
                            + isValid
                            + ", TimeDuration = "
                            + timeDuration);
            DataStallRecoveryStats.onDataStallEvent(mLastAction, mPhone, isValid, timeDuration);
            mLastActionReported = true;
        }

        if (isValid) {
            mLastActionReported = false;
            mDataStalled = false;
        }
    }

    /** Perform a series of data stall recovery actions. */
    private void doRecovery() {
        @RecoveryAction final int recoveryAction = getRecoveryAction();
        final int signalStrength = mPhone.getSignalStrength().getLevel();
        TelephonyMetrics.getInstance()
                .writeSignalStrengthEvent(mPhone.getPhoneId(), signalStrength);
        TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
        mLastAction = recoveryAction;
        mLastActionReported = false;
        broadcastDataStallDetected(recoveryAction);
        mNetworkCheckTimerStarted = false;

        switch (recoveryAction) {
            case RECOVERY_ACTION_GET_DATA_CALL_LIST:
                logl("doRecovery(): get data call list");
                getDataCallList();
                setRecoveryAction(RECOVERY_ACTION_CLEANUP);
                break;
            case RECOVERY_ACTION_CLEANUP:
                logl("doRecovery(): cleanup all connections");
                cleanUpDataNetwork();
                setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
                break;
            case RECOVERY_ACTION_RADIO_RESTART:
                logl("doRecovery(): restarting radio");
                setRecoveryAction(RECOVERY_ACTION_RESET_MODEM);
                powerOffRadio();
                break;
            case RECOVERY_ACTION_RESET_MODEM:
                logl("doRecovery(): modem reset");
                rebootModem();
                resetAction();
                break;
            default:
                throw new RuntimeException(
                        "doRecovery: Invalid recoveryAction = "
                                + recoveryActionToString(recoveryAction));
        }

        startNetworkCheckTimer();
    }

    /**
     * Convert RecoveryAction to string
     *
     * @param action The recovery action
     * @return The recovery action in string format.
     */
    private static @NonNull String recoveryActionToString(@RecoveryAction int action) {
        switch (action) {
            case RECOVERY_ACTION_GET_DATA_CALL_LIST:
                return "RECOVERY_ACTION_GET_DATA_CALL_LIST";
            case RECOVERY_ACTION_CLEANUP:
                return "RECOVERY_ACTION_CLEANUP";
            case RECOVERY_ACTION_RADIO_RESTART:
                return "RECOVERY_ACTION_RADIO_RESTART";
            case RECOVERY_ACTION_RESET_MODEM:
                return "RECOVERY_ACTION_RESET_MODEM";
            default:
                return "Unknown(" + action + ")";
        }
    }

    /** The data stall recovery config. */
@@ -325,14 +523,25 @@ public class DataStallRecoveryManager extends Handler {

    /**
     * Log debug messages.
     *
     * @param s debug messages
     */
    private void log(@NonNull String s) {
        Rlog.d(mLogTag, s);
    }

    /**
     * Log verbose messages.
     *
     * @param s debug messages.
     */
    private void logv(@NonNull String s) {
        if (VDBG) Rlog.v(mLogTag, s);
    }

    /**
     * Log error messages.
     *
     * @param s error messages
     */
    private void loge(@NonNull String s) {
@@ -341,6 +550,7 @@ public class DataStallRecoveryManager extends Handler {

    /**
     * Log debug messages and also log into the local log.
     *
     * @param s debug messages
     */
    private void logl(@NonNull String s) {
@@ -357,9 +567,19 @@ public class DataStallRecoveryManager extends Handler {
     */
    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println(DataStallRecoveryManager.class.getSimpleName() + "-" + mPhone.getPhoneId()
                + ":");
        pw.println(
                DataStallRecoveryManager.class.getSimpleName() + "-" + mPhone.getPhoneId() + ":");
        pw.increaseIndent();

        pw.println("mIsValidNetwork=" + mIsValidNetwork);
        pw.println("mDataStalled=" + mDataStalled);
        pw.println("mDataStallStartMs=" + mDataStallStartMs);
        pw.println("mRadioPowerState=" + mRadioPowerState);
        pw.println("mLastActionReported=" + mLastActionReported);
        pw.println("mTimeLastRecoveryStartMs=" + mTimeLastRecoveryStartMs);
        pw.println("getRecoveryAction()=" + recoveryActionToString(getRecoveryAction()));
        pw.println("");

        pw.println("Local logs:");
        pw.increaseIndent();
        mLocalLog.dump(fd, pw, args);
+174 −0

File added.

Preview size limit exceeded, changes collapsed.