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);