Support requesting restricted default

Carrier app will need to use the default cell network when the carrier
has restricted that (reprovisioning when out of bytes).  Add support
for bringing up internet with restricted capability, sidesteping
user/carrier enablement.

bug:28567303
Change-Id: I83fc29fa6707a7014ae1bc2e914cfa120f07c983
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index cf6c4c4..b448a10 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -38,6 +38,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -388,8 +389,9 @@
         }
     }
 
-    private final ArrayList<LocalLog> mLocalLogs = new ArrayList<LocalLog>();
-    private final ArrayDeque<LocalLog> mHistoryLogs = new ArrayDeque<LocalLog>();
+    private final ArrayList<LocalLog> mLocalLogs = new ArrayList<>();
+    private final ArrayList<NetworkRequest> mNetworkRequests = new ArrayList<>();
+    private final ArrayDeque<LocalLog> mHistoryLogs = new ArrayDeque<>();
     private final static int MAX_HISTORY_LOG_COUNT = 4;
 
     public void requestLog(String str) {
@@ -400,41 +402,59 @@
         }
     }
 
-    public void incRefCount(LocalLog log) {
+    public void requestNetwork(NetworkRequest networkRequest, LocalLog log) {
         synchronized (mRefCountLock) {
-            if (mLocalLogs.contains(log)) {
-                log.log("ApnContext.incRefCount has duplicate add - " + mRefCount);
+            if (mLocalLogs.contains(log) || mNetworkRequests.contains(networkRequest)) {
+                log.log("ApnContext.requestNetwork has duplicate add - " + mNetworkRequests.size());
             } else {
                 mLocalLogs.add(log);
-                log.log("ApnContext.incRefCount - " + mRefCount);
-            }
-            if (mRefCount++ == 0) {
-                mDcTracker.setEnabled(apnIdForApnName(mApnType), true);
+                mNetworkRequests.add(networkRequest);
+                if (mNetworkRequests.size() == 1) {
+                    mDcTracker.setEnabled(apnIdForApnName(mApnType), true);
+                }
             }
         }
     }
 
-    public void decRefCount(LocalLog log) {
+    public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
         synchronized (mRefCountLock) {
-            if (mLocalLogs.remove(log)) {
-                log.log("ApnContext.decRefCount - " + mRefCount);
-                mHistoryLogs.addFirst(log);
-                while (mHistoryLogs.size() > MAX_HISTORY_LOG_COUNT) {
-                    mHistoryLogs.removeLast();
-                }
+            if (mLocalLogs.contains(log) == false) {
+                log.log("ApnContext.releaseNetwork can't find this log");
             } else {
-                log.log("ApnContext.decRefCount didn't find log - " + mRefCount);
+                mLocalLogs.remove(log);
             }
-            if (mRefCount-- == 1) {
-                mDcTracker.setEnabled(apnIdForApnName(mApnType), false);
-            }
-            if (mRefCount < 0) {
-                log.log("ApnContext.decRefCount went to " + mRefCount);
-                mRefCount = 0;
+            if (mNetworkRequests.contains(networkRequest) == false) {
+                log.log("ApnContext.releaseNetwork can't find this request ("
+                        + networkRequest + ")");
+            } else {
+                mNetworkRequests.remove(networkRequest);
+                log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() +
+                        " requests.");
+                if (mNetworkRequests.size() == 0) {
+                    mDcTracker.setEnabled(apnIdForApnName(mApnType), false);
+                }
             }
         }
     }
 
+    public List<NetworkRequest> getNetworkRequests() {
+        synchronized (mRefCountLock) {
+            return new ArrayList<NetworkRequest>(mNetworkRequests);
+        }
+    }
+
+    public boolean hasNoRestrictedRequests() {
+        synchronized (mRefCountLock) {
+            for (NetworkRequest nr : mNetworkRequests) {
+                if (nr.networkCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) == false) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray();
 
     public void resetErrorCodeRetries() {
@@ -677,6 +697,14 @@
         final IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         synchronized (mRefCountLock) {
             pw.println(toString());
+            if (mNetworkRequests.size() > 0) {
+                pw.println("NetworkRequests:");
+                pw.increaseIndent();
+                for (NetworkRequest nr : mNetworkRequests) {
+                    pw.println(nr);
+                }
+                pw.decreaseIndent();
+            }
             pw.increaseIndent();
             for (LocalLog l : mLocalLogs) {
                 l.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 22a5c7c..8eac8c8 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -825,6 +825,56 @@
         mLinkProperties.setTcpBufferSizes(sizes);
     }
 
+    /**
+     * Indicates if when this connection was established we had a restricted/privileged
+     * NetworkRequest and needed it to overcome data-enabled limitations.
+     *
+     * This gets set once per connection setup and is based on conditions at that time.
+     * We could theoretically have dynamic capabilities but now is not a good time to
+     * experiement with that.
+     *
+     * This flag overrides the APN-based restriction capability, restricting the network
+     * based on both having a NetworkRequest with restricted AND needing a restricted
+     * bit to overcome user-disabled status.  This allows us to handle the common case
+     * of having both restricted requests and unrestricted requests for the same apn:
+     * if conditions require a restricted network to overcome user-disabled then it must
+     * be restricted, otherwise it is unrestricted (or restricted based on APN type).
+     *
+     * Because we're not supporting dynamic capabilities, if conditions change and we go from
+     * data-enabled to not or vice-versa we will need to tear down networks to deal with it
+     * at connection setup time with the new state.
+     *
+     * This supports a privileged app bringing up a network without general apps having access
+     * to it when the network is otherwise unavailable (hipri).  The first use case is
+     * pre-paid SIM reprovisioning over internet, where the carrier insists on no traffic
+     * other than from the privileged carrier-app.
+     */
+    private boolean mRestrictedNetworkOverride = false;
+
+    // Should be called once when the call goes active to examine the state of things and
+    // declare the restriction override for the life of the connection
+    private void setNetworkRestriction() {
+        mRestrictedNetworkOverride = false;
+        // first, if we have no restricted requests, this override can stay FALSE:
+        boolean noRestrictedRequests = true;
+        for (ApnContext apnContext : mApnContexts.keySet()) {
+            noRestrictedRequests &= apnContext.hasNoRestrictedRequests();
+        }
+        if (noRestrictedRequests) {
+            return;
+        }
+
+        // Do we need a restricted network to satisfy the request?
+        // Is this network metered?  If not, then don't add restricted
+        if (!mApnSetting.isMetered(mPhone.getContext(), mPhone.getSubId(),
+                mPhone.getServiceState().getDataRoaming())) {
+            return;
+        }
+
+        // Is user data disabled?
+        mRestrictedNetworkOverride = (mDct.isDataEnabled(true) == false);
+    }
+
     private NetworkCapabilities makeNetworkCapabilities() {
         NetworkCapabilities result = new NetworkCapabilities();
         result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
@@ -894,6 +944,10 @@
 
             result.maybeMarkCapabilitiesRestricted();
         }
+        if (mRestrictedNetworkOverride) {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+
         int up = 14;
         int down = 14;
         switch (mRilRat) {
@@ -1516,6 +1570,7 @@
             misc.subscriberId = mPhone.getSubscriberId();
 
             if (createNetworkAgent) {
+                setNetworkRestriction();
                 mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
                         "DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
                         50, misc);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 9862e9c..37968ec 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -952,14 +952,14 @@
         final int apnId = ApnContext.apnIdForNetworkRequest(networkRequest);
         final ApnContext apnContext = mApnContextsById.get(apnId);
         log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext);
-        if (apnContext != null) apnContext.incRefCount(log);
+        if (apnContext != null) apnContext.requestNetwork(networkRequest, log);
     }
 
     public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
         final int apnId = ApnContext.apnIdForNetworkRequest(networkRequest);
         final ApnContext apnContext = mApnContextsById.get(apnId);
         log.log("DcTracker.releaseNetwork for " + networkRequest + " found " + apnContext);
-        if (apnContext != null) apnContext.decRefCount(log);
+        if (apnContext != null) apnContext.releaseNetwork(networkRequest, log);
     }
 
     public boolean isApnSupported(String name) {
@@ -1337,7 +1337,7 @@
         return false;
     }
 
-    private boolean isDataEnabled(boolean checkUserDataEnabled) {
+    boolean isDataEnabled(boolean checkUserDataEnabled) {
         synchronized (mDataEnabledLock) {
             if (!(mInternalDataEnabled && (!checkUserDataEnabled || mUserDataEnabled)
                     && (!checkUserDataEnabled || sPolicyDataEnabled)))
@@ -1574,10 +1574,14 @@
         boolean isEmergencyApn = apnContext.getApnType().equals(PhoneConstants.APN_TYPE_EMERGENCY);
         final ServiceStateTracker sst = mPhone.getServiceStateTracker();
 
-        // set to false if apn type is non-metered.
+        // set to false if apn type is non-metered or if we have a restricted (priveleged)
+        // request for the network.
+        // TODO - may want restricted requests to only apply to carrier-limited data access
+        //        rather than applying to user limited as well.
         boolean checkUserDataEnabled =
-                (ApnSetting.isMeteredApnType(apnContext.getApnType(), mPhone.getContext(),
-                        mPhone.getSubId(), mPhone.getServiceState().getDataRoaming()));
+                ApnSetting.isMeteredApnType(apnContext.getApnType(), mPhone.getContext(),
+                        mPhone.getSubId(), mPhone.getServiceState().getDataRoaming()) &&
+                apnContext.hasNoRestrictedRequests();
 
         DataAllowFailReason failureReason = new DataAllowFailReason();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
index 319fe9d..7e07115 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
+import android.net.NetworkRequest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.LocalLog;
 
@@ -136,16 +138,18 @@
 
     @Test
     @SmallTest
-    public void testRefCount() throws Exception {
+    public void testNetworkRequest() throws Exception {
         LocalLog log = new LocalLog(3);
-        mApnContext.incRefCount(log);
+        NetworkCapabilities nc = new NetworkCapabilities();
+        NetworkRequest nr = new NetworkRequest(nc, 0, 0);
+        mApnContext.requestNetwork(nr, log);
         verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(true));
-        mApnContext.incRefCount(log);
+        mApnContext.requestNetwork(nr, log);
         verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(true));
 
-        mApnContext.decRefCount(log);
-        verify(mDcTracker, never()).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(false));
-        mApnContext.decRefCount(log);
+        mApnContext.releaseNetwork(nr, log);
+        verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(false));
+        mApnContext.releaseNetwork(nr, log);
         verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(false));
     }