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

Commit 5bd07bd5 authored by Kai Shi's avatar Kai Shi Committed by Android (Google) Code Review
Browse files

Merge "Add init version of LinkBandwidthEstimator" into sc-dev

parents 7ec4bcc8 9ec706ac
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ import com.android.internal.telephony.cdma.CdmaMmiCode;
import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
import com.android.internal.telephony.dataconnection.DataEnabledSettings;
import com.android.internal.telephony.dataconnection.DcTracker;
import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.gsm.GsmMmiCode;
@@ -323,6 +324,10 @@ public class GsmCdmaPhone extends Phone {
        SubscriptionController.getInstance().registerForUiccAppsEnabled(this,
                EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED, null, false);

        mLinkBandwidthEstimator = mTelephonyComponentFactory
                .inject(LinkBandwidthEstimator.class.getName())
                .makeLinkBandwidthEstimator(this);

        loadTtyMode();

        CallManager.getInstance().registerPhone(this);
+3 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.dataconnection.DataConnectionReasons;
import com.android.internal.telephony.dataconnection.DataEnabledSettings;
import com.android.internal.telephony.dataconnection.DcTracker;
import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -453,6 +454,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
    protected VoiceCallSessionStats mVoiceCallSessionStats;
    protected SmsStats mSmsStats;

    protected LinkBandwidthEstimator mLinkBandwidthEstimator;

    public IccRecords getIccRecords() {
        return mIccRecords.get();
    }
+9 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
import com.android.internal.telephony.cdma.EriManager;
import com.android.internal.telephony.dataconnection.DataEnabledSettings;
import com.android.internal.telephony.dataconnection.DcTracker;
import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
@@ -69,6 +70,7 @@ public class TelephonyComponentFactory {
    private static final String TAG = TelephonyComponentFactory.class.getSimpleName();

    private static TelephonyComponentFactory sInstance;
    private final TelephonyFacade mTelephonyFacade = new TelephonyFacade();

    private InjectedComponents mInjectedComponents;

@@ -447,4 +449,11 @@ public class TelephonyComponentFactory {
            SubscriptionController sc) {
        return new SubscriptionInfoUpdater(looper, context, sc);
    }

    /**
     * Create a new LinkBandwidthEstimator.
     */
    public LinkBandwidthEstimator makeLinkBandwidthEstimator(Phone phone) {
        return new LinkBandwidthEstimator(phone, mTelephonyFacade);
    }
}
+48 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import android.net.TrafficStats;
import android.os.SystemClock;

/**
 * This class is a wrapper of various static methods to simplify unit tests with static methods
 */
public class TelephonyFacade {
    /**
     * Returns milliseconds since boot, including time spent in sleep.
     *
     * @return Current time since boot in milliseconds.
     */
    public long getElapsedSinceBootMillis() {
        return SystemClock.elapsedRealtime();
    }

    /**
     * Wrapper for {@link TrafficStats#getMobileTxBytes}.
     */
    public long getMobileTxBytes() {
        return TrafficStats.getMobileTxBytes();
    }

    /**
     * Wrapper for {@link TrafficStats#getMobileRxBytes}.
     */
    public long getMobileRxBytes() {
        return TrafficStats.getMobileRxBytes();
    }
}
+365 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.dataconnection;

import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;

import android.annotation.NonNull;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message;
import android.os.OutcomeReceiver;
import android.telephony.CellSignalStrength;
import android.telephony.ModemActivityInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.view.Display;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyFacade;
import com.android.telephony.Rlog;

import java.util.List;

/**
 * Link Bandwidth Estimator based on the byte counts in TrafficStats and the time reported in modem
 * activity.
 */
public class LinkBandwidthEstimator extends Handler {
    private static final String TAG = LinkBandwidthEstimator.class.getSimpleName();
    private static final boolean DBG = false;
    @VisibleForTesting
    static final int MSG_SCREEN_STATE_CHANGED = 1;
    @VisibleForTesting
    static final int MSG_TRAFFIC_STATS_POLL = 2;
    @VisibleForTesting
    static final int MSG_MODEM_ACTIVITY_RETURNED = 3;
    @VisibleForTesting
    static final int MSG_DEFAULT_NETWORK_CHANGED = 4;
    private static final int MSG_SIGNAL_STRENGTH_CHANGED = 5;

    private static final int TRAFFIC_STATS_POLL_INTERVAL_MS = 1_000;
    private static final int MODEM_POLL_BYTE_DELTA_THR = 20_000;
    private static final int MODEM_POLL_BYTE_DELTA_ACC_THR = 500_000;
    private static final int MODEM_POLL_MIN_INTERVAL_MS = 5_000;
    private static final int MODEM_POLL_TIME_DELTA_MAX_MS = 15_000;
    private static final int TX_TIME_OVER_RX_TIME_RATIO_THR_NUM = 3;
    private static final int TX_TIME_OVER_RX_TIME_RATIO_THR_DEN = 2;

    private final Phone mPhone;
    private final TelephonyFacade mTelephonyFacade;
    private final TelephonyManager mTelephonyManager;
    private final ConnectivityManager mConnectivityManager;
    private boolean mScreenOn = false;
    private boolean mIsOnDefaultRoute = false;
    private long mLastModemPollTimeMs;

    private long mLastMobileTxBytes;
    private long mLastMobileRxBytes;
    private long mTxBytesDeltaAcc;
    private long mRxBytesDeltaAcc;
    private ModemActivityInfo mLastModemActivityInfo = null;
    private final PhoneStateListener mPhoneStateListener = new PhoneStateListenerImpl();
    private int mSignalStrengthDbm;
    private int mNetworkType;
    private NetworkCapabilities mNetworkCapabilities;
    private int mLinkBandwidthTxKps;
    private int mLinkBandwidthRxKps;

    private final DisplayManager.DisplayListener mDisplayListener =
            new DisplayManager.DisplayListener() {
                @Override
                public void onDisplayAdded(int displayId) { }

                @Override
                public void onDisplayRemoved(int displayId) { }

                @Override
                public void onDisplayChanged(int displayId) {
                    obtainMessage(MSG_SCREEN_STATE_CHANGED, isScreenOn()).sendToTarget();
                }
            };

    private final OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
            mOutcomeReceiver =
            new OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>() {
                @Override
                public void onResult(ModemActivityInfo result) {
                    obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, result).sendToTarget();
                }

                @Override
                public void onError(TelephonyManager.ModemActivityInfoException e) {
                    Rlog.e(TAG, "error reading modem stats:" + e);
                    obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, null).sendToTarget();
                }
            };

    private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
            new ConnectivityManager.NetworkCallback() {
                @Override
                public void onCapabilitiesChanged(@NonNull Network network,
                        @NonNull NetworkCapabilities networkCapabilities) {
                    obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, networkCapabilities).sendToTarget();
                }

                public void onLost(@NonNull Network network) {
                    obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, null).sendToTarget();
                }
            };

    public LinkBandwidthEstimator(Phone phone, TelephonyFacade telephonyFacade) {
        mPhone = phone;
        mTelephonyFacade = telephonyFacade;
        mTelephonyManager = phone.getContext()
                .getSystemService(TelephonyManager.class)
                .createForSubscriptionId(phone.getSubId());
        mConnectivityManager = phone.getContext().getSystemService(ConnectivityManager.class);
        DisplayManager dm = (DisplayManager) phone.getContext().getSystemService(
                Context.DISPLAY_SERVICE);
        dm.registerDisplayListener(mDisplayListener, null);
        handleScreenStateChange(isScreenOn());
        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
        mTelephonyManager.registerPhoneStateListener(new HandlerExecutor(this),
                mPhoneStateListener);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_SCREEN_STATE_CHANGED:
                handleScreenStateChange((boolean) msg.obj);
                break;
            case MSG_TRAFFIC_STATS_POLL:
                handleTrafficStatsPoll();
                break;
            case MSG_MODEM_ACTIVITY_RETURNED:
                handleModemActivityReturned((ModemActivityInfo) msg.obj);
                break;
            case MSG_DEFAULT_NETWORK_CHANGED:
                handleDefaultNetworkChanged((NetworkCapabilities) msg.obj);
                break;
            case MSG_SIGNAL_STRENGTH_CHANGED:
                handleSignalStrengthChange((SignalStrength) msg.obj);
                break;
            default:
                Rlog.e(TAG, "invalid message " + msg.what);
                break;
        }
    }

    /**
     * @return True if one the device's screen (e.g. main screen, wifi display, HDMI display etc...)
     * is on.
     */
    private boolean isScreenOn() {
        // Note that we don't listen to Intent.SCREEN_ON and Intent.SCREEN_OFF because they are no
        // longer adequate for monitoring the screen state since they are not sent in cases where
        // the screen is turned off transiently such as due to the proximity sensor.
        final DisplayManager dm = (DisplayManager) mPhone.getContext().getSystemService(
                Context.DISPLAY_SERVICE);
        Display[] displays = dm.getDisplays();

        if (displays != null) {
            for (Display display : displays) {
                // Anything other than STATE_ON is treated as screen off, such as STATE_DOZE,
                // STATE_DOZE_SUSPEND, etc...
                if (display.getState() == Display.STATE_ON) {
                    return true;
                }
            }
            return false;
        }

        return false;
    }

    private void handleScreenStateChange(boolean screenOn) {
        if (mScreenOn == screenOn) {
            return;
        }
        mScreenOn = screenOn;
        handleTrafficStatsPollConditionChanged();
    }

    private void handleDefaultNetworkChanged(NetworkCapabilities networkCapabilities) {
        mNetworkCapabilities = networkCapabilities;
        if (networkCapabilities == null) {
            mIsOnDefaultRoute = false;
        } else {
            mIsOnDefaultRoute = networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
        }
        handleTrafficStatsPollConditionChanged();
    }

    private void handleTrafficStatsPollConditionChanged() {
        if (mScreenOn && mIsOnDefaultRoute) {
            handleTrafficStatsPoll();
        } else {
            removeMessages(MSG_TRAFFIC_STATS_POLL);
        }
    }

    private void handleTrafficStatsPoll() {
        long mobileTxBytes = mTelephonyFacade.getMobileTxBytes();
        long mobileRxBytes = mTelephonyFacade.getMobileRxBytes();
        long txBytesDelta = mobileTxBytes - mLastMobileTxBytes;
        long rxBytesDelta = mobileRxBytes - mLastMobileRxBytes;
        mLastMobileTxBytes = mobileTxBytes;
        mLastMobileRxBytes = mobileRxBytes;
        mTxBytesDeltaAcc += txBytesDelta;
        mRxBytesDeltaAcc += rxBytesDelta;
        logd("TxByteDelta " + txBytesDelta + " RxByteDelta " + rxBytesDelta);
        logd("TxByteDeltaAcc " + mTxBytesDeltaAcc + " RxByteDeltaAcc " + mRxBytesDeltaAcc);
        // Schedule the next traffic stats poll
        sendEmptyMessageDelayed(MSG_TRAFFIC_STATS_POLL, TRAFFIC_STATS_POLL_INTERVAL_MS);

        // Check it meets the requirement to request modem activity
        if (txBytesDelta < MODEM_POLL_BYTE_DELTA_THR && rxBytesDelta < MODEM_POLL_BYTE_DELTA_THR
                && mTxBytesDeltaAcc < MODEM_POLL_BYTE_DELTA_ACC_THR
                && mRxBytesDeltaAcc < MODEM_POLL_BYTE_DELTA_ACC_THR) {
            return;
        }

        long timeSinceLastModemPoll = mTelephonyFacade.getElapsedSinceBootMillis()
                - mLastModemPollTimeMs;
        if (timeSinceLastModemPoll < MODEM_POLL_MIN_INTERVAL_MS) {
            return;
        }
        makeRequestModemActivity();
    }

    private void makeRequestModemActivity() {
        logd("modem activity requested");
        mLastModemPollTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
        mTelephonyManager.requestModemActivityInfo(Runnable::run, mOutcomeReceiver);
    }

    private void handleModemActivityReturned(ModemActivityInfo result) {
        logd("modem activity returned");
        updateBandwidthEst(result);

        mLastModemActivityInfo = result;
        resetByteDeltaAcc();
    }

    private void resetByteDeltaAcc() {
        mTxBytesDeltaAcc = 0;
        mRxBytesDeltaAcc = 0;
    }

    private void updateBandwidthEst(ModemActivityInfo modemActivityInfo) {
        if (mLastModemActivityInfo == null || modemActivityInfo == null
                || mNetworkCapabilities == null) {
            return;
        }
        long lastTimeMs = mLastModemActivityInfo.getTimestampMillis();
        long currTimeMs = modemActivityInfo.getTimestampMillis();
        long timeDeltaMs = currTimeMs - lastTimeMs;

        if (timeDeltaMs > MODEM_POLL_TIME_DELTA_MAX_MS || timeDeltaMs <= 0) {
            return;
        }
        ModemActivityInfo deltaInfo = mLastModemActivityInfo.getDelta(modemActivityInfo);
        long txTimeDeltaMs = getModemTxTimeMs(deltaInfo);
        long rxTimeDeltaMs = deltaInfo.getReceiveTimeMillis();
        long idleTimeDeltaMs = deltaInfo.getIdleTimeMillis();
        long sleepTimeDeltaMs = deltaInfo.getSleepTimeMillis();

        // Check if txTimeDeltaMs / rxTimeDeltaMs > TX_TIME_OVER_RX_TIME_RATIO_THR
        boolean isTxTimeOverRxTimeRatioLarge = (txTimeDeltaMs * TX_TIME_OVER_RX_TIME_RATIO_THR_DEN
                > rxTimeDeltaMs * TX_TIME_OVER_RX_TIME_RATIO_THR_NUM);
        long rxTimeBwEstMs = isTxTimeOverRxTimeRatioLarge
                ? (txTimeDeltaMs + rxTimeDeltaMs) : rxTimeDeltaMs;
        mLinkBandwidthTxKps = (int) (txTimeDeltaMs <= 0 ? 0 : mTxBytesDeltaAcc * 8 * 1000
                / txTimeDeltaMs / 1024);
        mLinkBandwidthRxKps = (int) (rxTimeBwEstMs <= 0 ? 0 : mRxBytesDeltaAcc * 8 * 1000
                / rxTimeBwEstMs / 1024);

        int l2TxTputKbps = mNetworkCapabilities.getLinkUpstreamBandwidthKbps();
        int l2RxTputKbps = mNetworkCapabilities.getLinkDownstreamBandwidthKbps();

        StringBuilder sb = new StringBuilder();
        logd(sb.append("dBm, ").append(mSignalStrengthDbm)
                .append(", ").append(mNetworkType)
                .append(", L2 txRxKbps, ").append(l2TxTputKbps)
                .append(", ").append(l2RxTputKbps)
                .append(", txRxSleepIdleMs, ").append(txTimeDeltaMs)
                .append(", ").append(rxTimeDeltaMs)
                .append(", ").append(sleepTimeDeltaMs)
                .append(", ").append(idleTimeDeltaMs)
                .append(", txRxKB, ").append(mTxBytesDeltaAcc / 1000)
                .append(", ").append(mRxBytesDeltaAcc / 1000)
                .append(", L3 txRxKbps, ").append(mLinkBandwidthTxKps)
                .append(", ").append(mLinkBandwidthRxKps)
                .toString());
    }

    private long getModemTxTimeMs(ModemActivityInfo modemActivity) {
        long txTimeMs = 0;
        for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
            txTimeMs += modemActivity.getTransmitDurationMillisAtPowerLevel(lvl);
        }
        return txTimeMs;
    }

    private void handleSignalStrengthChange(SignalStrength signalStrength) {
        List<CellSignalStrength> cssList =
                (signalStrength == null) ? null : signalStrength.getCellSignalStrengths();

        if (cssList == null || cssList.isEmpty()) {
            return;
        }

        CellSignalStrength primaryCss = cssList.get(0);
        mSignalStrengthDbm = primaryCss.getDbm();
        mNetworkType = mTelephonyManager.getDataNetworkType();
    }

    private class PhoneStateListenerImpl extends PhoneStateListener
            implements PhoneStateListener.SignalStrengthsChangedListener {
        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, signalStrength).sendToTarget();
        }
    }

    /**
     * @return the latest Tx link bandwidth estimate in Kbps
     */
    public int getTxLinkBandwidthKbps() {
        return mLinkBandwidthTxKps;
    }

    /**
     * @return the latest Rx link bandwidth estimate in Kbps
     */
    public int getRxLinkBandwidthKbps() {
        return mLinkBandwidthRxKps;
    }

    void logd(String msg) {
        if (DBG) Rlog.d(TAG, msg);
    }
}
Loading