5G Q backport
Meteredness (SubscriptionPlan, CarrierConfig)
Bandwidth estimates
Bug: 151630253
Test: DcTrackerTest, DataConnectionTest, SubscriptionManagerTest
Test: manual testing with cmd phone cc set-value that unmetered values
result in NET_CAPABILITY_NOT_METERED
Test: manual testing with TestServiceState that NR_NSA and NR_NSA_MMWAVE
bandwidths are updated when ServiceState changes
Change-Id: Id30bab0cb5fb16bbe31f1757176bf624381b332c
Merged-In: I83104fad4662a847c5f0aa1301676298c18e979b
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index eaa5593..0577e78 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -220,6 +220,7 @@
private RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
private RegistrantList mImsCapabilityChangedRegistrants = new RegistrantList();
private RegistrantList mNrStateChangedRegistrants = new RegistrantList();
+ private RegistrantList mNrFrequencyChangedRegistrants = new RegistrantList();
/* Radio power off pending flag and tag counter */
private boolean mPendingRadioPowerOffAfterDataOff = false;
@@ -1556,8 +1557,11 @@
}
mPhone.notifyPhysicalChannelConfiguration(list);
mLastPhysicalChannelConfigList = list;
- boolean hasChanged =
- updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS);
+ boolean hasChanged = false;
+ if (updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS)) {
+ mNrFrequencyChangedRegistrants.notifyRegistrants();
+ hasChanged = true;
+ }
if (updateNrStateFromPhysicalChannelConfigs(list, mSS)) {
mNrStateChangedRegistrants.notifyRegistrants();
hasChanged = true;
@@ -5585,6 +5589,25 @@
}
/**
+ * Registers for 5G NR frequency changed.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForNrFrequencyChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mNrFrequencyChangedRegistrants.add(r);
+ }
+
+ /**
+ * Unregisters for 5G NR frequency changed.
+ * @param h handler to notify
+ */
+ public void unregisterForNrFrequencyChanged(Handler h) {
+ mNrFrequencyChangedRegistrants.remove(h);
+ }
+
+ /**
* Get the NR data connection context ids.
*
* @return data connection context ids.
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 93a1ceb..c37be5c 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -265,6 +265,7 @@
private Object mUserData;
private int mSubscriptionOverride;
private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+ private boolean mUnmeteredOverride;
private int mDataRegState = Integer.MAX_VALUE;
private NetworkInfo mNetworkInfo;
@@ -318,9 +319,9 @@
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;
-
- private static final int CMD_TO_STRING_COUNT =
- EVENT_NR_STATE_CHANGED - BASE + 1;
+ static final int EVENT_DATA_CONNECTION_METEREDNESS_CHANGED = BASE + 28;
+ static final int EVENT_NR_FREQUENCY_CHANGED = BASE + 29;
+ private static final int CMD_TO_STRING_COUNT = EVENT_NR_FREQUENCY_CHANGED - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
@@ -356,8 +357,10 @@
"EVENT_REEVALUATE_RESTRICTED_STATE";
sCmdToString[EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE] =
"EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES";
- sCmdToString[EVENT_NR_STATE_CHANGED - BASE] =
- "EVENT_NR_STATE_CHANGED";
+ sCmdToString[EVENT_NR_STATE_CHANGED - BASE] = "EVENT_NR_STATE_CHANGED";
+ sCmdToString[EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE] =
+ "EVENT_DATA_CONNECTION_METEREDNESS_CHANGED";
+ sCmdToString[EVENT_NR_FREQUENCY_CHANGED - BASE] = "EVENT_NR_FREQUENCY_CHANGED";
}
// Convert cmd to string or null if unknown
static String cmdToString(int cmd) {
@@ -732,6 +735,14 @@
}
/**
+ * 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) {
+ sendMessage(obtainMessage(EVENT_DATA_CONNECTION_METEREDNESS_CHANGED, isUnmetered));
+ }
+
+ /**
* TearDown the data connection when the deactivation is complete a Message with
* msg.what == EVENT_DEACTIVATE_DONE
*
@@ -900,6 +911,8 @@
mDcFailCause = DataFailCause.NONE;
mDisabledApnTypeBitMask = 0;
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ mSubscriptionOverride = 0;
+ mUnmeteredOverride = false;
}
/**
@@ -1077,6 +1090,24 @@
mLinkProperties.setTcpBufferSizes(sizes);
}
+ private void updateLinkBandwidths(NetworkCapabilities caps, int rilRat) {
+ String ratName = ServiceState.rilRadioTechnologyToString(rilRat);
+ if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()) {
+ ratName = mPhone.getServiceState().getNrFrequencyRange()
+ == ServiceState.FREQUENCY_RANGE_MMWAVE
+ ? DctConstants.RAT_NAME_NR_NSA_MMWAVE : DctConstants.RAT_NAME_NR_NSA;
+ }
+
+ if (DBG) log("updateLinkBandwidths: " + ratName);
+
+ Pair<Integer, Integer> values = mDct.getLinkBandwidths(ratName);
+ if (values == null) {
+ values = new Pair<>(14, 14);
+ }
+ caps.setLinkDownstreamBandwidthKbps(values.first);
+ caps.setLinkUpstreamBandwidthKbps(values.second);
+ }
+
/**
* Indicates if this data connection was established for unmetered use only. Note that this
* flag should be populated when data becomes active. And if it is set to true, it can be set to
@@ -1278,29 +1309,7 @@
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
}
- int up = 14;
- int down = 14;
- switch (mRilRat) {
- case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: up = 80; down = 80; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: up = 59; down = 236; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: up = 384; down = 384; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: // fall through
- case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: up = 14; down = 14; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: up = 153; down = 2457; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: up = 1843; down = 3174; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: up = 100; down = 100; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: up = 2048; down = 14336; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: up = 5898; down = 14336; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: up = 5898; down = 14336; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: up = 1843; down = 5017; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: up = 51200; down = 102400; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: up = 51200; down = 102400; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: up = 153; down = 2516; break;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: up = 11264; down = 43008; break;
- default:
- }
- result.setLinkUpstreamBandwidthKbps(up);
- result.setLinkDownstreamBandwidthKbps(down);
+ updateLinkBandwidths(result, mRilRat);
result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mSubId)));
@@ -1317,6 +1326,11 @@
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
}
+ // Override set by DcTracker
+ if (mUnmeteredOverride) {
+ result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
+
return result;
}
@@ -1507,6 +1521,8 @@
DataConnection.EVENT_DATA_CONNECTION_ROAM_OFF, null, true);
mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
DataConnection.EVENT_NR_STATE_CHANGED, null);
+ mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
+ DataConnection.EVENT_NR_FREQUENCY_CHANGED, null);
// Add ourselves to the list of data connections
mDcController.addDc(DataConnection.this);
@@ -1521,6 +1537,8 @@
mPhone.getServiceStateTracker().unregisterForDataRoamingOn(getHandler());
mPhone.getServiceStateTracker().unregisterForDataRoamingOff(getHandler());
+ mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
+ mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
// Remove ourselves from the DC lists
mDcController.removeDc(DataConnection.this);
@@ -1604,6 +1622,14 @@
mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
}
break;
+ case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
+ boolean isUnmetered = (boolean) msg.obj;
+ if (isUnmetered == mUnmeteredOverride) {
+ break;
+ }
+ mUnmeteredOverride = isUnmetered;
+ // fallthrough
+ case EVENT_NR_FREQUENCY_CHANGED:
case EVENT_DATA_CONNECTION_ROAM_ON:
case EVENT_DATA_CONNECTION_ROAM_OFF:
case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
@@ -2182,6 +2208,14 @@
}
case EVENT_DATA_CONNECTION_ROAM_ON:
case EVENT_DATA_CONNECTION_ROAM_OFF:
+ case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
+ boolean isUnmetered = (boolean) msg.obj;
+ if (isUnmetered == mUnmeteredOverride) {
+ retVal = HANDLED;
+ break;
+ }
+ mUnmeteredOverride = isUnmetered;
+ // fallthrough
case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED: {
updateNetworkInfo();
if (mNetworkAgent != null) {
@@ -2197,13 +2231,21 @@
if (ar.exception != null) {
log("EVENT_BW_REFRESH_RESPONSE: error ignoring, e=" + ar.exception);
} else {
- final LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+ boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
+ mPhone.getContext().getResources().getString(com.android.internal.R
+ .string.config_bandwidthEstimateSource));
NetworkCapabilities nc = getNetworkCapabilities();
- if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
- nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
- if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
+ LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+ if (useModem && mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
+ if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+ nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
}
+ if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+ nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+ }
+ }
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
}
}
retVal = HANDLED;
@@ -2319,13 +2361,18 @@
if (ar.exception != null) {
loge("EVENT_LINK_CAPACITY_CHANGED e=" + ar.exception);
} else {
- LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+ boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
+ mPhone.getContext().getResources().getString(com.android.internal.R
+ .string.config_bandwidthEstimateSource));
NetworkCapabilities nc = getNetworkCapabilities();
- if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
- nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
- }
- if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
- nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+ if (useModem) {
+ LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+ if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+ nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
+ }
+ if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+ nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+ }
}
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
@@ -2931,6 +2978,7 @@
pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
pw.println("disallowedApnTypes="
+ ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
+ pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
pw.println("mInstanceNumber=" + mInstanceNumber);
pw.println("mAc=" + mAc);
pw.println("mScore=" + mScore);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index ed2bc82..5901985 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -18,10 +18,8 @@
import android.content.Context;
import android.hardware.radio.V1_4.DataConnActiveStatus;
-import android.net.INetworkPolicyListener;
import android.net.LinkAddress;
import android.net.LinkProperties.CompareResult;
-import android.net.NetworkPolicyManager;
import android.net.NetworkUtils;
import android.os.AsyncResult;
import android.os.Build;
@@ -69,7 +67,6 @@
private DccDefaultState mDccDefaultState = new DccDefaultState();
final TelephonyManager mTelephonyManager;
- final NetworkPolicyManager mNetworkPolicyManager;
private PhoneStateListener mPhoneStateListener;
@@ -107,8 +104,6 @@
mTelephonyManager = (TelephonyManager) phone.getContext()
.getSystemService(Context.TELEPHONY_SERVICE);
- mNetworkPolicyManager = (NetworkPolicyManager) phone.getContext()
- .getSystemService(Context.NETWORK_POLICY_SERVICE);
mDcTesterDeactivateAll = (Build.IS_DEBUGGABLE)
? new DcTesterDeactivateAll(mPhone, DcController.this, getHandler())
@@ -173,21 +168,6 @@
return mExecutingCarrierChange;
}
- private final INetworkPolicyListener mListener = new NetworkPolicyManager.Listener() {
- @Override
- public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
- if (mPhone == null || mPhone.getSubId() != subId) return;
-
- final HashMap<Integer, DataConnection> dcListActiveByCid;
- synchronized (mDcListAll) {
- dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
- }
- for (DataConnection dc : dcListActiveByCid.values()) {
- dc.onSubscriptionOverride(overrideMask, overrideValue);
- }
- }
- };
-
private class DccDefaultState extends State {
@Override
public void enter() {
@@ -199,10 +179,6 @@
mDataServiceManager.registerForDataCallListChanged(getHandler(),
DataConnection.EVENT_DATA_STATE_CHANGED);
-
- if (mNetworkPolicyManager != null) {
- mNetworkPolicyManager.registerListener(mListener);
- }
}
@Override
@@ -216,9 +192,6 @@
if (mDcTesterDeactivateAll != null) {
mDcTesterDeactivateAll.dispose();
}
- if (mNetworkPolicyManager != null) {
- mNetworkPolicyManager.unregisterListener(mListener);
- }
}
@Override
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 31c4846..a9d652e 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -41,10 +41,12 @@
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
+import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TrafficStats;
@@ -77,7 +79,9 @@
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.TelephonyManager.NetworkType;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.data.ApnSetting;
import android.telephony.data.ApnSetting.ApnType;
@@ -116,6 +120,7 @@
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;
@@ -331,6 +336,25 @@
private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
+ /* Default for 5G connection reevaluation alarm durations */
+ private int mHysteresisTimeSec = 0;
+ private long mWatchdogTimeMs = 1000 * 60 * 60;
+
+ /* Default for whether 5G frequencies are considered unmetered */
+ private boolean mAllUnmetered = false;
+ private boolean mMmwaveUnmetered = false;
+ private boolean mSub6Unmetered = false;
+
+ /* Used to check whether 5G timers are currently active and waiting to go off */
+ 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;
+
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
@Override
public void onReceive(Context context, Intent intent) {
@@ -343,12 +367,16 @@
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
+ reevaluateUnmeteredConnections();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (DBG) log("screen off");
mIsScreenOn = false;
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
+ stopHysteresisAlarm();
+ stopWatchdogAlarm();
+ setDataConnectionUnmetered(false);
} else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
onActionIntentReconnectAlarm(intent);
} else if (action.equals(INTENT_DATA_STALL_ALARM)) {
@@ -361,6 +389,35 @@
if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
setDefaultDataRoamingEnabled();
}
+ String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
+ CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+ boolean useLte = false;
+ CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY)
+ != null) {
+ bandwidths = b.getStringArray(
+ CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+ }
+ useLte = b.getBoolean(CarrierConfigManager
+ .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL);
+ mHysteresisTimeSec = b.getInt(
+ CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
+ mWatchdogTimeMs = b.getLong(
+ CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+ mAllUnmetered = b.getBoolean(
+ CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
+ mMmwaveUnmetered = b.getBoolean(
+ CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
+ mSub6Unmetered = b.getBoolean(
+ CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
+ }
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_UPDATE_CARRIER_CONFIGS,
+ useLte ? 1 : 0, 0 /* unused */, bandwidths));
} else {
if (DBG) log("onReceive: Unknown action=" + action);
}
@@ -414,6 +471,27 @@
}
};
+ private NetworkPolicyManager mNetworkPolicyManager;
+ private final INetworkPolicyListener mNetworkPolicyListener =
+ new NetworkPolicyManager.Listener() {
+ @Override
+ public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
+ if (mPhone == null || mPhone.getSubId() != subId) return;
+
+ 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;
private void registerSettingsObserver() {
@@ -622,6 +700,10 @@
private ArrayList<DataProfile> mLastDataProfileList = new ArrayList<>();
+ /** RAT name ===> (downstream, upstream) bandwidth values from carrier config. */
+ private final ConcurrentHashMap<String, Pair<Integer, Integer>> mBandwidths =
+ new ConcurrentHashMap<>();
+
/**
* Handles changes to the APN db.
*/
@@ -727,6 +809,9 @@
mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ mNetworkPolicyManager = NetworkPolicyManager.from(mPhone.getContext());
+ mNetworkPolicyManager.registerListener(mNetworkPolicyListener);
+
HandlerThread dcHandlerThread = new HandlerThread("DcHandlerThread");
dcHandlerThread.start();
Handler dcHandler = new Handler(dcHandlerThread.getLooper());
@@ -788,6 +873,8 @@
DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
DctConstants.EVENT_DATA_RAT_CHANGED, null);
+ // listens for PhysicalChannelConfig changes
+ mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_SERVICE_STATE_CHANGED, null);
}
public void unregisterServiceStateTrackerEvents() {
@@ -799,6 +886,7 @@
mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
this);
+ mPhone.unregisterForServiceStateChanged(this);
}
private void registerForAllEvents() {
@@ -844,6 +932,7 @@
mSubscriptionManager
.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ mNetworkPolicyManager.unregisterListener(mNetworkPolicyListener);
mDcc.dispose();
mDcTesterFailBringUpAll.dispose();
@@ -3804,6 +3893,20 @@
case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
onDataEnabledOverrideRulesChanged();
break;
+ case DctConstants.EVENT_SERVICE_STATE_CHANGED:
+ reevaluateUnmeteredConnections();
+ break;
+ case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
+ reevaluateUnmeteredConnections();
+ mHysteresis = false;
+ break;
+ case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+ mWatchdog = false;
+ reevaluateUnmeteredConnections();
+ break;
+ case DctConstants.EVENT_UPDATE_CARRIER_CONFIGS:
+ updateLinkBandwidths((String[]) msg.obj, msg.arg1 == 1);
+ break;
default:
Rlog.e("DcTracker", "Unhandled event=" + msg);
break;
@@ -3874,6 +3977,57 @@
}
/**
+ * Update link bandwidth estimate default values from carrier config.
+ * @param bandwidths String array of "RAT:upstream,downstream" for each RAT
+ * @param useLte For NR NSA, whether to use LTE value for upstream or not
+ */
+ private void updateLinkBandwidths(String[] bandwidths, boolean useLte) {
+ synchronized (mBandwidths) {
+ mBandwidths.clear();
+ for (String config : bandwidths) {
+ int downstream = 14;
+ int upstream = 14;
+ String[] kv = config.split(":");
+ if (kv.length == 2) {
+ String[] split = kv[1].split(",");
+ if (split.length == 2) {
+ try {
+ downstream = Integer.parseInt(split[0]);
+ upstream = Integer.parseInt(split[1]);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ mBandwidths.put(kv[0], new Pair<>(downstream, upstream));
+ }
+ }
+ if (useLte) {
+ Pair<Integer, Integer> ltePair = mBandwidths.get(DctConstants.RAT_NAME_LTE);
+ if (ltePair != null) {
+ if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA)) {
+ mBandwidths.put(DctConstants.RAT_NAME_NR_NSA, new Pair<>(
+ mBandwidths.get(DctConstants.RAT_NAME_NR_NSA).first,
+ ltePair.second));
+ }
+ if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA_MMWAVE)) {
+ mBandwidths.put(DctConstants.RAT_NAME_NR_NSA_MMWAVE, new Pair<>(
+ mBandwidths.get(DctConstants.RAT_NAME_NR_NSA_MMWAVE).first,
+ ltePair.second));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the link upstream/downstream values from CarrierConfig for the given RAT name.
+ * @param ratName RAT name from ServiceState#rilRadioTechnologyToString.
+ * @return pair of downstream/upstream values (kbps), or null if the config is not defined.
+ */
+ public Pair<Integer, Integer> getLinkBandwidths(String ratName) {
+ return mBandwidths.get(ratName);
+ }
+
+ /**
* Update DcTracker.
*
* TODO: This should be cleaned up. DcTracker should listen to those events.
@@ -3962,6 +4116,101 @@
}
}
+ private void reevaluateUnmeteredConnections() {
+ if (isNetworkTypeUnmetered(NETWORK_TYPE_NR) || isFrequencyRangeUnmetered()) {
+ if (DBG) log("NR NSA is unmetered");
+ if (mPhone.getServiceState().getNrState()
+ == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+ if (!m5GWasConnected) { // 4G -> 5G
+ stopHysteresisAlarm();
+ setDataConnectionUnmetered(true);
+ }
+ if (!mWatchdog) {
+ startWatchdogAlarm();
+ }
+ m5GWasConnected = true;
+ } else {
+ if (m5GWasConnected) { // 5G -> 4G
+ if (!mHysteresis && !startHysteresisAlarm()) {
+ // hysteresis is not active but carrier does not support hysteresis
+ stopWatchdogAlarm();
+ setDataConnectionUnmetered(isNetworkTypeUnmetered(
+ mTelephonyManager.getNetworkType(mPhone.getSubId())));
+ }
+ m5GWasConnected = false;
+ } else { // 4G -> 4G
+ if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
+ stopWatchdogAlarm();
+ setDataConnectionUnmetered(isNetworkTypeUnmetered(
+ mTelephonyManager.getNetworkType(mPhone.getSubId())));
+ }
+ // do nothing if waiting for hysteresis alarm to go off
+ }
+ }
+ } else {
+ stopWatchdogAlarm();
+ stopHysteresisAlarm();
+ setDataConnectionUnmetered(isNetworkTypeUnmetered(
+ mTelephonyManager.getNetworkType(mPhone.getSubId())));
+ m5GWasConnected = false;
+ }
+ }
+
+ private void setDataConnectionUnmetered(boolean isUnmetered) {
+ for (DataConnection dataConnection : mDataConnections.values()) {
+ dataConnection.onMeterednessChanged(isUnmetered);
+ }
+ }
+
+ private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
+ if (mSubscriptionPlans == null || mSubscriptionPlans.size() == 0) {
+ // safe return false if unable to get subscription plans or plans don't exist
+ return false;
+ }
+
+ long bitmask = ServiceState.getBitmaskForTech(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 boolean isFrequencyRangeUnmetered() {
+ boolean nrConnected = mPhone.getServiceState().getNrState()
+ == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+ if (mMmwaveUnmetered || mSub6Unmetered) {
+ int frequencyRange = mPhone.getServiceState().getNrFrequencyRange();
+ boolean mmwave = frequencyRange == ServiceState.FREQUENCY_RANGE_MMWAVE;
+ // frequency range LOW, MID, or HIGH
+ boolean sub6 = frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN && !mmwave;
+ return (mMmwaveUnmetered && mmwave || mSub6Unmetered && sub6) && nrConnected;
+ } else {
+ return mAllUnmetered && nrConnected;
+ }
+ }
+
private void log(String s) {
Rlog.d(mLogTag, s);
}
@@ -4778,6 +5027,36 @@
}
}
+ /**
+ * 5G connection reevaluation alarms
+ */
+ private boolean startHysteresisAlarm() {
+ if (mHysteresisTimeSec > 0) {
+ // only create hysteresis alarm if CarrierConfig allows it
+ sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
+ mHysteresisTimeSec * 1000);
+ mHysteresis = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void stopHysteresisAlarm() {
+ removeMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS);
+ mHysteresis = false;
+ }
+
+ private void startWatchdogAlarm() {
+ sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG), mWatchdogTimeMs);
+ mWatchdog = true;
+ }
+
+ private void stopWatchdogAlarm() {
+ removeMessages(DctConstants.EVENT_5G_TIMER_WATCHDOG);
+ mWatchdog = false;
+ }
+
private static DataProfile createDataProfile(ApnSetting apn, boolean isPreferred) {
return createDataProfile(apn, apn.getProfileId(), isPreferred);
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index a6eacc9..e2774d7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -51,6 +51,7 @@
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.AsyncResult;
@@ -69,6 +70,7 @@
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;
@@ -98,9 +100,15 @@
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 {
@@ -146,6 +154,8 @@
PackageManagerService mMockPackageManagerInternal;
@Mock
Handler mHandler;
+ @Mock
+ NetworkPolicyManager mNetworkPolicyManager;
private DcTracker mDct;
private DcTrackerTestHandler mDcTrackerTestHandler;
@@ -504,7 +514,8 @@
}
).when(mSubscriptionManager).addOnSubscriptionsChangedListener(any());
doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
-
+ doReturn(mNetworkPolicyManager).when(mContext)
+ .getSystemService(Context.NETWORK_POLICY_SERVICE);
doReturn(1).when(mIsub).getDefaultDataSubId();
doReturn(mIsub).when(mBinder).queryLocalInterface(anyString());
mServiceManagerMockedServices.put("isub", mBinder);
@@ -1781,4 +1792,276 @@
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 int setUpDataConnection() throws Exception {
+ Field dc = DcTracker.class.getDeclaredField("mDataConnections");
+ dc.setAccessible(true);
+ Field uig = DcTracker.class.getDeclaredField("mUniqueIdGenerator");
+ uig.setAccessible(true);
+ int id = ((AtomicInteger) uig.get(mDct)).getAndIncrement();
+ ((HashMap<Integer, DataConnection>) dc.get(mDct)).put(id, mDataConnection);
+ return id;
+ }
+
+ private void resetDataConnection(int id) throws Exception {
+ Field dc = DcTracker.class.getDeclaredField("mDataConnections");
+ dc.setAccessible(true);
+ ((HashMap<Integer, DataConnection>) dc.get(mDct)).remove(id);
+ }
+
+ 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);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ }
+
+ 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 testIsFrequencyRangeUnmetered() throws Exception {
+ initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+ int id = setUpDataConnection();
+ setUpSubscriptionPlans(false);
+ setUpWatchdogTimer();
+ doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+ // NetCapability should be metered when connected to 5G with no unmetered plan or frequency
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ verify(mDataConnection, times(1)).onMeterednessChanged(false);
+
+ // Set MMWAVE frequency to unmetered
+ mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, true);
+ Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+ mContext.sendBroadcast(intent);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+ // NetCapability should switch to unmetered when fr=MMWAVE and MMWAVE unmetered
+ doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ verify(mDataConnection, times(1)).onMeterednessChanged(true);
+
+ // NetCapability should switch to metered when fr=SUB6 and MMWAVE unmetered
+ doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ verify(mDataConnection, times(2)).onMeterednessChanged(false);
+
+ // Set SUB6 frequency to unmetered
+ mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL, true);
+ intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+ mContext.sendBroadcast(intent);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+ // NetCapability should switch to unmetered when fr=SUB6 and SUB6 unmetered
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ verify(mDataConnection, times(2)).onMeterednessChanged(true);
+
+ resetDataConnection(id);
+ }
+
+ @Test
+ public void testReevaluateUnmeteredConnectionsOnNetworkChange() throws Exception {
+ initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+ int id = 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ verify(mDataConnection, times(1)).onMeterednessChanged(false);
+
+ resetDataConnection(id);
+ }
+
+ @Test
+ public void testReevaluateUnmeteredConnectionsOnHysteresis() throws Exception {
+ initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+ int id = setUpDataConnection();
+ setUpSubscriptionPlans(true);
+ setUpWatchdogTimer();
+
+ // Hysteresis active for 10s
+ mBundle.putInt(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);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+ // 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertTrue(getHysteresisStatus());
+
+ // NetCapability metered when hysteresis timer goes off
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertFalse(getHysteresisStatus());
+
+ // Hysteresis disabled
+ mBundle.putInt(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);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+ // 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertFalse(getHysteresisStatus());
+
+ resetDataConnection(id);
+ }
+
+ @Test
+ public void testReevaluateUnmeteredConnectionsOnWatchdog() throws Exception {
+ initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+ int id = 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertFalse(getWatchdogStatus());
+
+ // Hysteresis active for 10s
+ mBundle.putInt(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);
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+ // 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ 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));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertTrue(getHysteresisStatus());
+ assertTrue(getWatchdogStatus());
+
+ // Watchdog inactive when metered
+ setUpSubscriptionPlans(false);
+ mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+ waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+ assertFalse(getWatchdogStatus());
+
+ resetDataConnection(id);
+ }
}