Handle 5G meteredness in telephony framework
Created 5G connection reevaluation logic in DcTracker.
Listens for changes in SubscriptionOverride and connectivity.
Watchdog and hysteresis alarms to reevaluate connection conditions.
Bug: 139070884
Bug: 139218397
Test: atest FrameworksTelephonyTests
Change-Id: I864084d96600ca5d88a3afd354d1091ee4cef176
Merged-In: I864084d96600ca5d88a3afd354d1091ee4cef176
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index 7db4d26..caaed64 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.Handler;
@@ -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 = (TelephonyUtils.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 9ea5c5d..36cf9e5 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -17,6 +17,8 @@
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;
@@ -41,10 +43,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;
@@ -67,6 +71,7 @@
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.DataFailureCause;
+import android.telephony.Annotation.NetworkType;
import android.telephony.CarrierConfigManager;
import android.telephony.CellLocation;
import android.telephony.DataFailCause;
@@ -333,6 +338,20 @@
private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
+ /* Default for 5G connection reevaluation alarm durations */
+ private long mHysteresisTimeMs = 0;
+ private long mWatchdogTimeMs = 1000 * 60 * 60;
+
+ /* Used to check whether 5G timers are currently active and waiting to go off */
+ private boolean mHysteresis = false;
+ private boolean mWatchdog = false;
+
+ /* 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) {
@@ -361,6 +380,17 @@
if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
setDefaultDataRoamingEnabled();
}
+ CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ mHysteresisTimeMs = b.getLong(
+ CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
+ mWatchdogTimeMs = b.getLong(
+ CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+ }
+ }
} else {
if (DBG) log("onReceive: Unknown action=" + action);
}
@@ -414,6 +444,23 @@
}
};
+ private NetworkPolicyManager mNetworkPolicyManager;
+ private final INetworkPolicyListener mNetworkPolicyListener =
+ new NetworkPolicyManager.Listener() {
+ @Override
+ public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
+ long networkTypeMask) {
+ if (mPhone == null || mPhone.getSubId() != subId) return;
+
+ if (overrideMask == OVERRIDE_UNMETERED) {
+ mUnmeteredOverrideBitMask = overrideValue == 0 ? 0 : networkTypeMask;
+ reevaluateUnmeteredConnections();
+ } else {
+ overrideDataConnections(overrideMask, overrideValue);
+ }
+ }
+ };
+
private final SettingsObserver mSettingsObserver;
private void registerSettingsObserver() {
@@ -695,6 +742,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());
@@ -749,6 +799,7 @@
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);
}
public void unregisterServiceStateTrackerEvents() {
@@ -760,6 +811,7 @@
mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
this);
+ mPhone.unregisterForServiceStateChanged(this);
}
private void registerForAllEvents() {
@@ -805,6 +857,7 @@
mSubscriptionManager
.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ mNetworkPolicyManager.unregisterListener(mNetworkPolicyListener);
mDcc.dispose();
mDcTesterFailBringUpAll.dispose();
@@ -857,6 +910,12 @@
}
}
+ private void overrideDataConnections(int overrideMask, int overrideValue) {
+ for (DataConnection dataConnection : mDataConnections.values()) {
+ dataConnection.onSubscriptionOverride(overrideMask, overrideValue);
+ }
+ }
+
public long getSubId() {
return mPhone.getSubId();
}
@@ -3755,6 +3814,17 @@
case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
onDataEnabledOverrideRulesChanged();
break;
+ case DctConstants.EVENT_5G_NETWORK_CHANGED:
+ reevaluateUnmeteredConnections();
+ break;
+ case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
+ reevaluateUnmeteredConnections();
+ mHysteresis = false;
+ break;
+ case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+ mWatchdog = false;
+ reevaluateUnmeteredConnections();
+ break;
default:
Rlog.e("DcTracker", "Unhandled event=" + msg);
break;
@@ -3913,6 +3983,53 @@
}
}
+ private void reevaluateUnmeteredConnections() {
+ if (isNetworkTypeUnmetered(NETWORK_TYPE_NR)) {
+ if (mPhone.getServiceState().getNrState() == NR_STATE_CONNECTED) {
+ if (!m5GWasConnected) { //4G -> 5G
+ stopHysteresisAlarm();
+ overrideDataConnections(OVERRIDE_UNMETERED, OVERRIDE_UNMETERED);
+ }
+ if (!mWatchdog) {
+ startWatchdogAlarm();
+ }
+ m5GWasConnected = true;
+ } else {
+ if (m5GWasConnected) { //5G -> 4G
+ if (!mHysteresis && !startHysteresisAlarm()) {
+ // hysteresis is not active but carrier does not support hysteresis
+ stopWatchdogAlarm();
+ overrideMeterednessForNetworkType(
+ mTelephonyManager.getNetworkType(mPhone.getSubId()));
+ }
+ m5GWasConnected = false;
+ } else { //4G -> 4G
+ if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
+ stopWatchdogAlarm();
+ overrideMeterednessForNetworkType(
+ mTelephonyManager.getNetworkType(mPhone.getSubId()));
+ }
+ // do nothing if waiting for hysteresis alarm to go off
+ }
+ }
+ } else {
+ stopWatchdogAlarm();
+ stopHysteresisAlarm();
+ overrideMeterednessForNetworkType(mTelephonyManager.getNetworkType(mPhone.getSubId()));
+ m5GWasConnected = false;
+ }
+ }
+
+ private void overrideMeterednessForNetworkType(@NetworkType int networkType) {
+ int overrideValue = isNetworkTypeUnmetered(networkType) ? OVERRIDE_UNMETERED : 0;
+ overrideDataConnections(OVERRIDE_UNMETERED, overrideValue);
+ }
+
+ private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
+ long networkTypeMask = TelephonyManager.getBitMaskForNetworkType(networkType);
+ return (mUnmeteredOverrideBitMask & networkTypeMask) == networkTypeMask;
+ }
+
private void log(String s) {
Rlog.d(mLogTag, s);
}
@@ -4745,6 +4862,37 @@
}
}
+ /**
+ * 5G connection reevaluation alarms
+ */
+ private boolean startHysteresisAlarm() {
+ if (mHysteresisTimeMs > 0) {
+ // only create hysteresis alarm if CarrierConfig allows it
+ sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
+ mHysteresisTimeMs);
+ 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 8dbbb5b..bd7301e 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;
@@ -147,6 +148,8 @@
PackageManagerService mMockPackageManagerInternal;
@Mock
Handler mHandler;
+ @Mock
+ NetworkPolicyManager mNetworkPolicyManager;
private DcTracker mDct;
private DcTrackerTestHandler mDcTrackerTestHandler;
@@ -505,7 +508,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);