Merge "Add ability to override subscriber capabilities."
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 58b8e62..80d483b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -173,6 +176,7 @@
     private DcFailCause mLastFailCause;
     private static final String NULL_IP = "0.0.0.0";
     private Object mUserData;
+    private int mSubscriptionOverride;
     private int mRilRat = Integer.MAX_VALUE;
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
@@ -203,9 +207,10 @@
     static final int EVENT_BW_REFRESH_RESPONSE = BASE + 14;
     static final int EVENT_DATA_CONNECTION_VOICE_CALL_STARTED = BASE + 15;
     static final int EVENT_DATA_CONNECTION_VOICE_CALL_ENDED = BASE + 16;
+    static final int EVENT_DATA_CONNECTION_OVERRIDE_CHANGED = BASE + 17;
 
     private static final int CMD_TO_STRING_COUNT =
-            EVENT_DATA_CONNECTION_VOICE_CALL_ENDED - BASE + 1;
+            EVENT_DATA_CONNECTION_OVERRIDE_CHANGED - BASE + 1;
 
     private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
     static {
@@ -229,6 +234,8 @@
                 "EVENT_DATA_CONNECTION_VOICE_CALL_STARTED";
         sCmdToString[EVENT_DATA_CONNECTION_VOICE_CALL_ENDED - BASE] =
                 "EVENT_DATA_CONNECTION_VOICE_CALL_ENDED";
+        sCmdToString[EVENT_DATA_CONNECTION_OVERRIDE_CHANGED - BASE] =
+                "EVENT_DATA_CONNECTION_OVERRIDE_CHANGED";
     }
     // Convert cmd to string or null if unknown
     static String cmdToString(int cmd) {
@@ -511,6 +518,12 @@
         mPhone.mCi.setupDataCall(cp.mRilRat, dp, isModemRoaming, allowRoaming, msg);
     }
 
+    public void onSubscriptionOverride(int overrideMask, int overrideValue) {
+        mSubscriptionOverride = (mSubscriptionOverride & ~overrideMask)
+                | (overrideValue & overrideMask);
+        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_OVERRIDE_CHANGED));
+    }
+
     /**
      * TearDown the data connection when the deactivation is complete a Message with
      * msg.what == EVENT_DEACTIVATE_DONE and msg.obj == AsyncResult with AsyncResult.obj
@@ -1006,14 +1019,19 @@
 
         result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mPhone.getSubId())));
 
-        if (!mPhone.getServiceState().getDataRoaming()) {
-            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        } else {
-            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        }
+        result.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
+                !mPhone.getServiceState().getDataRoaming());
 
         result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
 
+        // Override values set above when requested by policy
+        if ((mSubscriptionOverride & OVERRIDE_UNMETERED) != 0) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        }
+        if ((mSubscriptionOverride & OVERRIDE_CONGESTED) != 0) {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
+        }
+
         return result;
     }
 
@@ -1343,6 +1361,7 @@
                     break;
                 case EVENT_DATA_CONNECTION_ROAM_ON:
                 case EVENT_DATA_CONNECTION_ROAM_OFF:
+                case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
                     updateNetworkInfo();
                     if (mNetworkAgent != null) {
                         mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
@@ -1791,7 +1810,8 @@
                     break;
                 }
                 case EVENT_DATA_CONNECTION_ROAM_ON:
-                case EVENT_DATA_CONNECTION_ROAM_OFF: {
+                case EVENT_DATA_CONNECTION_ROAM_OFF:
+                case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED: {
                     updateNetworkInfo();
                     if (mNetworkAgent != null) {
                         mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
@@ -2237,6 +2257,7 @@
         pw.println("mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime));
         pw.println("mLastFailCause=" + mLastFailCause);
         pw.println("mUserData=" + mUserData);
+        pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
         pw.println("mInstanceNumber=" + mInstanceNumber);
         pw.println("mAc=" + mAc);
         pw.println("Network capabilities changed history:");
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index c21713f..b4ceff6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -17,8 +17,10 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.content.Context;
+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;
@@ -55,9 +57,10 @@
     private DcTesterDeactivateAll mDcTesterDeactivateAll;
 
     // package as its used by Testing code
-    ArrayList<DataConnection> mDcListAll = new ArrayList<DataConnection>();
-    private HashMap<Integer, DataConnection> mDcListActiveByCid =
-            new HashMap<Integer, DataConnection>();
+    // @GuardedBy("mDcListAll")
+    final ArrayList<DataConnection> mDcListAll = new ArrayList<>();
+    // @GuardedBy("mDcListAll")
+    private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>();
 
     /**
      * Constants for the data connection activity:
@@ -72,7 +75,9 @@
 
     private DccDefaultState mDccDefaultState = new DccDefaultState();
 
-    TelephonyManager mTelephonyManager;
+    final TelephonyManager mTelephonyManager;
+    final NetworkPolicyManager mNetworkPolicyManager;
+
     private PhoneStateListener mPhoneStateListener;
 
     //mExecutingCarrierChange tracks whether the phone is currently executing
@@ -105,8 +110,12 @@
             }
         };
 
-        mTelephonyManager = (TelephonyManager) phone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        if(mTelephonyManager != null) {
+        mTelephonyManager = (TelephonyManager) phone.getContext()
+                .getSystemService(Context.TELEPHONY_SERVICE);
+        mNetworkPolicyManager = (NetworkPolicyManager) phone.getContext()
+                .getSystemService(Context.NETWORK_POLICY_SERVICE);
+
+        if (mTelephonyManager != null) {
             mTelephonyManager.listen(mPhoneStateListener,
                     PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
         }
@@ -125,29 +134,39 @@
     }
 
     void addDc(DataConnection dc) {
-        mDcListAll.add(dc);
+        synchronized (mDcListAll) {
+            mDcListAll.add(dc);
+        }
     }
 
     void removeDc(DataConnection dc) {
-        mDcListActiveByCid.remove(dc.mCid);
-        mDcListAll.remove(dc);
+        synchronized (mDcListAll) {
+            mDcListActiveByCid.remove(dc.mCid);
+            mDcListAll.remove(dc);
+        }
     }
 
     public void addActiveDcByCid(DataConnection dc) {
         if (DBG && dc.mCid < 0) {
             log("addActiveDcByCid dc.mCid < 0 dc=" + dc);
         }
-        mDcListActiveByCid.put(dc.mCid, dc);
+        synchronized (mDcListAll) {
+            mDcListActiveByCid.put(dc.mCid, dc);
+        }
     }
 
     public DataConnection getActiveDcByCid(int cid) {
-        return mDcListActiveByCid.get(cid);
+        synchronized (mDcListAll) {
+            return mDcListActiveByCid.get(cid);
+        }
     }
 
     void removeActiveDcByCid(DataConnection dc) {
-        DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
-        if (DBG && removedDc == null) {
-            log("removeActiveDcByCid removedDc=null dc=" + dc);
+        synchronized (mDcListAll) {
+            DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
+            if (DBG && removedDc == null) {
+                log("removeActiveDcByCid removedDc=null dc=" + dc);
+            }
         }
     }
 
@@ -155,6 +174,21 @@
         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() {
@@ -166,6 +200,9 @@
                 mDcTesterDeactivateAll =
                         new DcTesterDeactivateAll(mPhone, DcController.this, getHandler());
             }
+            if (mNetworkPolicyManager != null) {
+                mNetworkPolicyManager.registerListener(mListener);
+            }
         }
 
         @Override
@@ -177,6 +214,9 @@
             if (mDcTesterDeactivateAll != null) {
                 mDcTesterDeactivateAll.dispose();
             }
+            if (mNetworkPolicyManager != null) {
+                mNetworkPolicyManager.unregisterListener(mListener);
+            }
         }
 
         @Override
@@ -214,12 +254,19 @@
          * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
          */
         private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
+            final ArrayList<DataConnection> dcListAll;
+            final HashMap<Integer, DataConnection> dcListActiveByCid;
+            synchronized (mDcListAll) {
+                dcListAll = new ArrayList<>(mDcListAll);
+                dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
+            }
+
             if (DBG) {
                 lr("onDataStateChanged: dcsList=" + dcsList
-                        + " mDcListActiveByCid=" + mDcListActiveByCid);
+                        + " dcListActiveByCid=" + dcListActiveByCid);
             }
             if (VDBG) {
-                log("onDataStateChanged: mDcListAll=" + mDcListAll);
+                log("onDataStateChanged: mDcListAll=" + dcListAll);
             }
 
             // Create hashmap of cid to DataCallResponse
@@ -232,7 +279,7 @@
             // Add a DC that is active but not in the
             // dcsList to the list of DC's to retry
             ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
-            for (DataConnection dc : mDcListActiveByCid.values()) {
+            for (DataConnection dc : dcListActiveByCid.values()) {
                 if (dataCallResponseListByCid.get(dc.mCid) == null) {
                     if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
                     dcsToRetry.add(dc);
@@ -249,7 +296,7 @@
 
             for (DataCallResponse newState : dcsList) {
 
-                DataConnection dc = mDcListActiveByCid.get(newState.getCallId());
+                DataConnection dc = dcListActiveByCid.get(newState.getCallId());
                 if (dc == null) {
                     // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
                     loge("onDataStateChanged: no associated DC yet, ignore");
@@ -437,14 +484,18 @@
 
     @Override
     public String toString() {
-        return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
+        synchronized (mDcListAll) {
+            return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
+        }
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
         pw.println(" mPhone=" + mPhone);
-        pw.println(" mDcListAll=" + mDcListAll);
-        pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
+        synchronized (mDcListAll) {
+            pw.println(" mDcListAll=" + mDcListAll);
+            pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
+        }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index 3decf92..98b3deb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -16,6 +16,11 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_ADDRESS;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_DNS;
@@ -26,8 +31,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -362,6 +367,48 @@
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
     }
 
+    @Test
+    public void testOverrideUnmetered() throws Exception {
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[] { "default" });
+        testConnectEvent();
+
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+
+        mDc.onSubscriptionOverride(OVERRIDE_UNMETERED, OVERRIDE_UNMETERED);
+
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+
+        mDc.onSubscriptionOverride(OVERRIDE_UNMETERED, 0);
+
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+    }
+
+    @Test
+    public void testOverrideCongested() throws Exception {
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[] { "default" });
+        testConnectEvent();
+
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+
+        mDc.onSubscriptionOverride(OVERRIDE_CONGESTED, OVERRIDE_CONGESTED);
+
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+
+        mDc.onSubscriptionOverride(OVERRIDE_CONGESTED, 0);
+
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+    }
+
     @SmallTest
     public void testIsIpAddress() throws Exception {
         // IPv4