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

Commit aad2183c authored by Sarah Chin's avatar Sarah Chin Committed by android-build-merger
Browse files

Merge "5G meteredness for telephony framework"

am: aaba3602

Change-Id: I8ad1782f480f8a82be809c02342c6bd7ae552e14
parents b7721352 aaba3602
Loading
Loading
Loading
Loading
+24 −2
Original line number Diff line number Diff line
@@ -267,6 +267,7 @@ public class DataConnection extends StateMachine {
    private static final String NULL_IP = "0.0.0.0";
    private Object mUserData;
    private int mSubscriptionOverride;
    private boolean mUnmeteredOverride;
    private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
    private int mDataRegState = Integer.MAX_VALUE;
    private NetworkInfo mNetworkInfo;
@@ -321,9 +322,9 @@ public class DataConnection extends StateMachine {
    static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25;
    static final int EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES = BASE + 26;
    static final int EVENT_NR_STATE_CHANGED = BASE + 27;

    static final int EVENT_DATA_CONNECTION_METEREDNESS_CHANGED = BASE + 28;
    private static final int CMD_TO_STRING_COUNT =
            EVENT_NR_STATE_CHANGED - BASE + 1;
            EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE + 1;

    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
    static {
@@ -361,6 +362,8 @@ public class DataConnection extends StateMachine {
                "EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES";
        sCmdToString[EVENT_NR_STATE_CHANGED - BASE] =
                "EVENT_NR_STATE_CHANGED";
        sCmdToString[EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE] =
                "EVENT_DATA_CONNECTION_METEREDNESS_CHANGED";
    }
    // Convert cmd to string or null if unknown
    static String cmdToString(int cmd) {
@@ -741,6 +744,15 @@ public class DataConnection extends StateMachine {
        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_OVERRIDE_CHANGED));
    }

    /**
     * Update NetworkCapabilities.NET_CAPABILITY_NOT_METERED based on meteredness
     * @param isUnmetered whether this DC should be set to unmetered or not
     */
    public void onMeterednessChanged(boolean isUnmetered) {
        mUnmeteredOverride = isUnmetered;
        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_METEREDNESS_CHANGED));
    }

    /**
     * TearDown the data connection when the deactivation is complete a Message with
     * msg.what == EVENT_DEACTIVATE_DONE
@@ -910,6 +922,8 @@ public class DataConnection extends StateMachine {
        mDcFailCause = DataFailCause.NONE;
        mDisabledApnTypeBitMask = 0;
        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        mSubscriptionOverride = 0;
        mUnmeteredOverride = false;
    }

    /**
@@ -1331,6 +1345,11 @@ public class DataConnection extends StateMachine {
            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
        }

        // Override set by DcTracker
        if (mUnmeteredOverride) {
            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        }

        return result;
    }

@@ -1633,6 +1652,7 @@ public class DataConnection extends StateMachine {
                    break;
                case EVENT_DATA_CONNECTION_ROAM_ON:
                case EVENT_DATA_CONNECTION_ROAM_OFF:
                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
                case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
                    updateNetworkInfo();
                    if (mNetworkAgent != null) {
@@ -2228,6 +2248,7 @@ public class DataConnection extends StateMachine {
                }
                case EVENT_DATA_CONNECTION_ROAM_ON:
                case EVENT_DATA_CONNECTION_ROAM_OFF:
                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
                case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED: {
                    updateNetworkInfo();
                    if (mNetworkAgent != null) {
@@ -2978,6 +2999,7 @@ public class DataConnection extends StateMachine {
        pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
        pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
        pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
        pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
        pw.println("disallowedApnTypes="
                + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
        pw.println("mInstanceNumber=" + mInstanceNumber);
+68 −37
Original line number Diff line number Diff line
@@ -17,8 +17,6 @@
package com.android.internal.telephony.dataconnection;

import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
import static android.telephony.NetworkRegistrationInfo.NR_STATE_CONNECTED;
import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;

@@ -83,6 +81,7 @@ import android.telephony.ServiceState;
import android.telephony.ServiceState.RilRadioTechnology;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.data.ApnSetting;
@@ -122,6 +121,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -346,12 +346,12 @@ public class DcTracker extends Handler {
    private boolean mHysteresis = false;
    private boolean mWatchdog = false;

    /* List of SubscriptionPlans, updated on SubscriptionManager.setSubscriptionPlans */
    private List<SubscriptionPlan> mSubscriptionPlans = null;

    /* Used to check whether phone was recently connected to 5G. */
    private boolean m5GWasConnected = false;

    /* Used to keep track of unmetered overrides per network type */
    private long mUnmeteredOverrideBitMask = 0;

    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -448,17 +448,21 @@ public class DcTracker extends Handler {
    private final INetworkPolicyListener mNetworkPolicyListener =
            new NetworkPolicyManager.Listener() {
        @Override
        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
                long networkTypeMask) {
        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
            if (mPhone == null || mPhone.getSubId() != subId) return;

            if (overrideMask == OVERRIDE_UNMETERED) {
                mUnmeteredOverrideBitMask = overrideValue == 0 ? 0 : networkTypeMask;
                reevaluateUnmeteredConnections();
            } else {
                overrideDataConnections(overrideMask, overrideValue);
            for (DataConnection dataConnection : mDataConnections.values()) {
                dataConnection.onSubscriptionOverride(overrideMask, overrideValue);
            }
        }

        @Override
        public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
            if (mPhone == null || mPhone.getSubId() != subId) return;

            mSubscriptionPlans = plans == null ? null : Arrays.asList(plans);
            reevaluateUnmeteredConnections();
        }
    };

    private final SettingsObserver mSettingsObserver;
@@ -799,7 +803,8 @@ public class DcTracker extends Handler {
                DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
                DctConstants.EVENT_DATA_RAT_CHANGED, null);
        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_5G_NETWORK_CHANGED, null);
        // listens for PhysicalChannelConfig changes
        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_SERVICE_STATE_CHANGED, null);
    }

    public void unregisterServiceStateTrackerEvents() {
@@ -910,12 +915,6 @@ public class DcTracker extends Handler {
        }
    }

    private void overrideDataConnections(int overrideMask, int overrideValue) {
        for (DataConnection dataConnection : mDataConnections.values()) {
            dataConnection.onSubscriptionOverride(overrideMask, overrideValue);
        }
    }

    public long getSubId() {
        return mPhone.getSubId();
    }
@@ -3814,7 +3813,7 @@ public class DcTracker extends Handler {
            case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
                onDataEnabledOverrideRulesChanged();
                break;
            case DctConstants.EVENT_5G_NETWORK_CHANGED:
            case DctConstants.EVENT_SERVICE_STATE_CHANGED:
                reevaluateUnmeteredConnections();
                break;
            case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
@@ -3985,10 +3984,11 @@ public class DcTracker extends Handler {

    private void reevaluateUnmeteredConnections() {
        if (isNetworkTypeUnmetered(NETWORK_TYPE_NR)) {
            if (mPhone.getServiceState().getNrState() == NR_STATE_CONNECTED) {
            if (mPhone.getServiceState().getNrState()
                    == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
                if (!m5GWasConnected) { // 4G -> 5G
                    stopHysteresisAlarm();
                    overrideDataConnections(OVERRIDE_UNMETERED, OVERRIDE_UNMETERED);
                    setDataConnectionUnmetered(true);
                }
                if (!mWatchdog) {
                    startWatchdogAlarm();
@@ -3999,15 +3999,15 @@ public class DcTracker extends Handler {
                    if (!mHysteresis && !startHysteresisAlarm()) {
                        // hysteresis is not active but carrier does not support hysteresis
                        stopWatchdogAlarm();
                        overrideMeterednessForNetworkType(
                                mTelephonyManager.getNetworkType(mPhone.getSubId()));
                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
                    }
                    m5GWasConnected = false;
                } else { // 4G -> 4G
                    if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
                        stopWatchdogAlarm();
                        overrideMeterednessForNetworkType(
                                mTelephonyManager.getNetworkType(mPhone.getSubId()));
                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
                    }
                    // do nothing if waiting for hysteresis alarm to go off
                }
@@ -4015,19 +4015,51 @@ public class DcTracker extends Handler {
        } else {
            stopWatchdogAlarm();
            stopHysteresisAlarm();
            overrideMeterednessForNetworkType(mTelephonyManager.getNetworkType(mPhone.getSubId()));
            setDataConnectionUnmetered(isNetworkTypeUnmetered(
                    mTelephonyManager.getNetworkType(mPhone.getSubId())));
            m5GWasConnected = false;
        }
    }

    private void overrideMeterednessForNetworkType(@NetworkType int networkType) {
        int overrideValue = isNetworkTypeUnmetered(networkType) ? OVERRIDE_UNMETERED : 0;
        overrideDataConnections(OVERRIDE_UNMETERED, overrideValue);
    private void setDataConnectionUnmetered(boolean isUnmetered) {
        for (DataConnection dataConnection : mDataConnections.values()) {
            dataConnection.onMeterednessChanged(isUnmetered);
        }
    }

    private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
        long networkTypeMask = TelephonyManager.getBitMaskForNetworkType(networkType);
        return (mUnmeteredOverrideBitMask & networkTypeMask) == networkTypeMask;
        if (mSubscriptionPlans == null || mSubscriptionPlans.size() == 0) {
            // safe return false if unable to get subscription plans or plans don't exist
            return false;
        }

        long bitmask = TelephonyManager.getBitMaskForNetworkType(networkType);
        boolean isGeneralUnmetered = true;
        for (SubscriptionPlan plan : mSubscriptionPlans) {
            // check plan applies to given network type
            if ((plan.getNetworkTypesBitMask() & bitmask) == bitmask) {
                // check plan is general or specific
                if (plan.getNetworkTypes() == null) {
                    if (!isPlanUnmetered(plan)) {
                        // metered takes precedence over unmetered for safety
                        isGeneralUnmetered = false;
                    }
                } else {
                    // ensure network type unknown returns general value
                    if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
                        // there is only 1 specific plan per network type, so return value if found
                        return isPlanUnmetered(plan);
                    }
                }
            }
        }
        return isGeneralUnmetered;
    }

    private boolean isPlanUnmetered(SubscriptionPlan plan) {
        return plan.getDataLimitBytes() == SubscriptionPlan.BYTES_UNLIMITED
                && (plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN
                || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED);
    }

    private void log(String s) {
@@ -4883,8 +4915,7 @@ public class DcTracker extends Handler {
    }

    private void startWatchdogAlarm() {
        sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG),
                mWatchdogTimeMs);
        sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG), mWatchdogTimeMs);
        mWatchdog = true;
    }

+219 −0
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.data.DataProfile;
@@ -99,9 +100,15 @@ import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class DcTrackerTest extends TelephonyTest {

@@ -1796,4 +1803,216 @@ public class DcTrackerTest extends TelephonyTest {
        assertEquals(reason, (int) result.second);
        clearInvocations(mHandler);
    }

    private void setUpSubscriptionPlans(boolean is5GUnmetered) throws Exception {
        List<SubscriptionPlan> plans = new ArrayList<>();
        if (is5GUnmetered) {
            plans.add(SubscriptionPlan.Builder
                    .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
                            Period.ofMonths(1))
                    .setTitle("Some NR 5G unmetered workaround plan")
                    .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
                            SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
                    .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_NR})
                    .build());
        }
        plans.add(SubscriptionPlan.Builder
                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
                        Period.ofMonths(1))
                .setTitle("Some 5GB Plan")
                .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
                .setDataUsage(500_000_000, System.currentTimeMillis())
                .build());
        replaceInstance(DcTracker.class, "mSubscriptionPlans", mDct, plans);
    }

    private boolean isNetworkTypeUnmetered(int networkType) throws Exception {
        Method method = DcTracker.class.getDeclaredMethod(
                "isNetworkTypeUnmetered", int.class);
        method.setAccessible(true);
        return (boolean) method.invoke(mDct, networkType);
    }

    private void setUpDataConnection() throws Exception {
        Field dc = DcTracker.class.getDeclaredField("mDataConnections");
        dc.setAccessible(true);
        Field uig = DcTracker.class.getDeclaredField("mUniqueIdGenerator");
        uig.setAccessible(true);
        ((HashMap<Integer, DataConnection>) dc.get(mDct)).put(
                ((AtomicInteger) uig.get(mDct)).getAndIncrement(), mDataConnection);
    }

    private void setUpWatchdogTimer() {
        // Watchdog active for 10s
        mBundle.putLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG, 10000);
        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
        mContext.sendBroadcast(intent);
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
    }

    private boolean getHysteresisStatus() throws Exception {
        Field field = DcTracker.class.getDeclaredField(("mHysteresis"));
        field.setAccessible(true);
        return (boolean) field.get(mDct);
    }

    private boolean getWatchdogStatus() throws Exception {
        Field field = DcTracker.class.getDeclaredField(("mWatchdog"));
        field.setAccessible(true);
        return (boolean) field.get(mDct);
    }

    @Test
    public void testIsNetworkTypeUnmetered() throws Exception {
        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});

        // only 5G unmetered
        setUpSubscriptionPlans(true);

        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));

        // all network types metered
        setUpSubscriptionPlans(false);

        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));

        // all network types unmetered
        List<SubscriptionPlan> plans = new ArrayList<>();
        plans.add(SubscriptionPlan.Builder
                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
                        Period.ofMonths(1))
                .setTitle("Some 5GB Plan")
                .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
                        SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
                .build());
        replaceInstance(DcTracker.class, "mSubscriptionPlans", mDct, plans);

        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));
    }

    @Test
    public void testReevaluateUnmeteredConnectionsOnNetworkChange() throws Exception {
        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
        setUpDataConnection();
        setUpSubscriptionPlans(true);
        setUpWatchdogTimer();

        // NetCapability should be unmetered when connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        verify(mDataConnection, times(1)).onMeterednessChanged(true);

        // NetCapability should be metered when disconnected from 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        verify(mDataConnection, times(1)).onMeterednessChanged(false);
    }

    @Test
    public void testReevaluateUnmeteredConnectionsOnHysteresis() throws Exception {
        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
        setUpDataConnection();
        setUpSubscriptionPlans(true);
        setUpWatchdogTimer();

        // Hysteresis active for 10s
        mBundle.putLong(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
        mContext.sendBroadcast(intent);
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());

        // Hysteresis inactive when unmetered and never connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getHysteresisStatus());

        // Hysteresis inactive when unmetered and connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getHysteresisStatus());

        // Hysteresis active when unmetered and disconnected after connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertTrue(getHysteresisStatus());

        // NetCapability metered when hysteresis timer goes off
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getHysteresisStatus());
        verify(mDataConnection, times(1)).onMeterednessChanged(true);

        // Hysteresis inactive when reconnected after timer goes off
        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getHysteresisStatus());

        // Hysteresis disabled
        mBundle.putLong(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0);
        intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
        mContext.sendBroadcast(intent);
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());

        // Hysteresis inactive when CarrierConfig is set to 0
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getHysteresisStatus());
    }

    @Test
    public void testReevaluateUnmeteredConnectionsOnWatchdog() throws Exception {
        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
        setUpDataConnection();
        setUpSubscriptionPlans(true);
        setUpWatchdogTimer();

        // Watchdog inactive when unmetered and never connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getWatchdogStatus());

        // Hysteresis active for 10s
        mBundle.putLong(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
        mContext.sendBroadcast(intent);
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());

        // Watchdog active when unmetered and connected to 5G
        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertTrue(getWatchdogStatus());
        assertFalse(getHysteresisStatus());

        // Watchdog active during hysteresis
        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertTrue(getHysteresisStatus());
        assertTrue(getWatchdogStatus());

        // Watchdog inactive when metered
        setUpSubscriptionPlans(false);
        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
        assertFalse(getWatchdogStatus());
    }
}