Loading src/java/com/android/internal/telephony/dataconnection/DataConnection.java +24 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -910,6 +922,8 @@ public class DataConnection extends StateMachine { mDcFailCause = DataFailCause.NONE; mDisabledApnTypeBitMask = 0; mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mSubscriptionOverride = 0; mUnmeteredOverride = false; } /** Loading Loading @@ -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; } Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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); Loading src/java/com/android/internal/telephony/dataconnection/DcTracker.java +68 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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(); } Loading Loading @@ -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: Loading Loading @@ -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(); Loading @@ -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 } Loading @@ -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) { Loading Loading @@ -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; } Loading tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java +219 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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()); } } Loading
src/java/com/android/internal/telephony/dataconnection/DataConnection.java +24 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -910,6 +922,8 @@ public class DataConnection extends StateMachine { mDcFailCause = DataFailCause.NONE; mDisabledApnTypeBitMask = 0; mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mSubscriptionOverride = 0; mUnmeteredOverride = false; } /** Loading Loading @@ -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; } Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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); Loading
src/java/com/android/internal/telephony/dataconnection/DcTracker.java +68 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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(); } Loading Loading @@ -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: Loading Loading @@ -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(); Loading @@ -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 } Loading @@ -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) { Loading Loading @@ -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; } Loading
tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java +219 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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()); } }