Add single-pdn arbitration.

If a device or network can only support a single data connection
then we need to tear down lower priority connections in favor
of higher priority requests.

bug:10212445
Change-Id: I5ff38ae3be993aee51b6716045e6857b4f2942af
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 20927b9..f4e26ea 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -103,6 +103,7 @@
     static final String REASON_DATA_DEPENDENCY_UNMET = "dependencyUnmet";
     static final String REASON_LOST_DATA_CONNECTION = "lostDataConnection";
     static final String REASON_CONNECTED = "connected";
+    static final String REASON_SINGLE_PDN_ARBITRATION = "SinglePdnArbitration";
 
     // Used for band mode selection methods
     static final int BM_UNSPECIFIED = 0; // selected by baseband automatically
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 312d033..16aad87 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.content.Context;
+import android.net.NetworkConfig;
 import android.telephony.Rlog;
 
 import com.android.internal.R;
@@ -47,6 +48,8 @@
 
     private ArrayList<ApnSetting> mWaitingApns = null;
 
+    public final int priority;
+
     /** A zero indicates that all waiting APNs had a permanent error */
     private AtomicInteger mWaitingApnsPermanentFailureCountDown;
 
@@ -68,14 +71,15 @@
      */
     AtomicBoolean mDependencyMet;
 
-    public ApnContext(Context context, String apnType, String logTag) {
+    public ApnContext(Context context, String apnType, String logTag, NetworkConfig config) {
         mContext = context;
         mApnType = apnType;
         mState = DctConstants.State.IDLE;
         setReason(Phone.REASON_DATA_ENABLED);
         mDataEnabled = new AtomicBoolean(false);
-        mDependencyMet = new AtomicBoolean(true);
+        mDependencyMet = new AtomicBoolean(config.dependencyMet);
         mWaitingApnsPermanentFailureCountDown = new AtomicInteger(0);
+        priority = config.priority;
         LOG_TAG = logTag;
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 15c75b0..ae026b7 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -35,6 +35,7 @@
 import android.net.ProxyProperties;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Build;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.SystemClock;
@@ -190,6 +191,7 @@
 
         mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
         mApnContexts.clear();
+        mPrioritySortedApnContexts.clear();
 
         destroyDataConnections();
     }
@@ -230,9 +232,9 @@
     }
 
     private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
-        ApnContext apnContext = new ApnContext(mPhone.getContext(), type, LOG_TAG);
-        apnContext.setDependencyMet(networkConfig.dependencyMet);
+        ApnContext apnContext = new ApnContext(mPhone.getContext(), type, LOG_TAG, networkConfig);
         mApnContexts.put(type, apnContext);
+        mPrioritySortedApnContexts.add(apnContext);
         return apnContext;
     }
 
@@ -596,7 +598,10 @@
     }
 
     private void setupDataOnConnectableApns(String reason) {
-        for (ApnContext apnContext : mApnContexts.values()) {
+        if (DBG) log("setupDataOnConnectableApns: " + reason);
+
+        for (ApnContext apnContext : mPrioritySortedApnContexts) {
+            if (DBG) log("setupDataOnConnectableApns: apnContext " + apnContext);
             if (apnContext.getState() == DctConstants.State.FAILED) {
                 apnContext.setState(DctConstants.State.IDLE);
             }
@@ -608,28 +613,6 @@
         }
     }
 
-    private boolean trySetupData(String reason, String type) {
-        if (DBG) {
-            log("trySetupData: " + type + " due to " + (reason == null ? "(unspecified)" : reason)
-                    + " isPsRestricted=" + mIsPsRestricted);
-        }
-
-        if (type == null) {
-            type = PhoneConstants.APN_TYPE_DEFAULT;
-        }
-
-        ApnContext apnContext = mApnContexts.get(type);
-
-        if (apnContext == null ){
-            if (DBG) log("trySetupData new apn context for type:" + type);
-            apnContext = new ApnContext(mPhone.getContext(), type, LOG_TAG);
-            mApnContexts.put(type, apnContext);
-        }
-        apnContext.setReason(reason);
-
-        return trySetupData(apnContext);
-    }
-
     private boolean trySetupData(ApnContext apnContext) {
         if (DBG) {
             log("trySetupData for type:" + apnContext.getApnType() +
@@ -720,11 +703,16 @@
      * @param tearDown true if the underlying DataConnection should be
      * disconnected.
      * @param reason reason for the clean up.
+     * @return boolean - true if we did cleanup any connections, false if they
+     *                   were already all disconnected.
      */
-    protected void cleanUpAllConnections(boolean tearDown, String reason) {
+    protected boolean cleanUpAllConnections(boolean tearDown, String reason) {
         if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
+        boolean didDisconnect = false;
 
         for (ApnContext apnContext : mApnContexts.values()) {
+            if (apnContext.isDisconnected() == false) didDisconnect = true;
+            // TODO - only do cleanup if not disconnected
             apnContext.setReason(reason);
             cleanUpConnection(tearDown, apnContext);
         }
@@ -734,6 +722,7 @@
 
         // TODO: Do we need mRequestedApnType?
         mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+        return didDisconnect;
     }
 
     /**
@@ -1033,6 +1022,29 @@
             }
         }
         if (dcac == null) {
+            if (isOnlySingleDcAllowed(radioTech)) {
+                if (isHigherPriorityApnContextActive(apnContext)) {
+                    if (DBG) {
+                        log("setupData: Higher priority ApnContext active.  Ignoring call");
+                    }
+                    return false;
+                }
+
+                // Only lower priority calls left.  Disconnect them all in this single PDP case
+                // so that we can bring up the requested higher priority call (once we receive
+                // repsonse for deactivate request for the calls we are about to disconnect
+                if (cleanUpAllConnections(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
+                    // If any call actually requested to be disconnected, means we can't
+                    // bring up this connection yet as we need to wait for those data calls
+                    // to be disconnected.
+                    if (DBG) log("setupData: Some calls are disconnecting first.  Wait and retry");
+                    return false;
+                }
+
+                // No other calls are active, so proceed
+                if (DBG) log("setupData: Single pdp. Continue setting up data call.");
+            }
+
             dcac = findFreeDataConnection();
 
             if (dcac == null) {
@@ -1166,6 +1178,45 @@
         mActiveApn = null;
     }
 
+    /**
+     * "Active" here means ApnContext isEnabled() and not in FAILED state
+     * @param apnContext to compare with
+     * @return true if higher priority active apn found
+     */
+    private boolean isHigherPriorityApnContextActive(ApnContext apnContext) {
+        for (ApnContext otherContext : mPrioritySortedApnContexts) {
+            if (apnContext.getApnType().equalsIgnoreCase(otherContext.getApnType())) return false;
+            if (otherContext.isEnabled() && otherContext.getState() != DctConstants.State.FAILED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Reports if we support multiple connections or not.
+     * This is a combination of factors, based on carrier and RAT.
+     * @param rilRadioTech the RIL Radio Tech currently in use
+     * @return true if only single DataConnection is allowed
+     */
+    private boolean isOnlySingleDcAllowed(int rilRadioTech) {
+        int[] singleDcRats = mPhone.getContext().getResources().getIntArray(
+                com.android.internal.R.array.config_onlySingleDcAllowed);
+        boolean onlySingleDcAllowed = false;
+        if (Build.IS_DEBUGGABLE &&
+                SystemProperties.getBoolean("persist.telephony.test.singleDc", false)) {
+            onlySingleDcAllowed = true;
+        }
+        if (singleDcRats != null) {
+            for (int i=0; i < singleDcRats.length && onlySingleDcAllowed == false; i++) {
+                if (rilRadioTech == singleDcRats[i]) onlySingleDcAllowed = true;
+            }
+        }
+
+        if (DBG) log("isOnlySingleDcAllowed(" + rilRadioTech + "): " + onlySingleDcAllowed);
+        return onlySingleDcAllowed;
+    }
+
     @Override
     protected void restartRadio() {
         if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
@@ -1190,10 +1241,13 @@
      * @param reason the reason why data is disconnected
      * @return true if try setup data connection is need for this reason
      */
-    private boolean retryAfterDisconnected(String reason) {
+    private boolean retryAfterDisconnected(ApnContext apnContext) {
         boolean retry = true;
+        String reason = apnContext.getReason();
 
-        if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ) {
+        if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ||
+                (isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology())
+                 && isHigherPriorityApnContextActive(apnContext))) {
             retry = false;
         }
         return retry;
@@ -1764,8 +1818,7 @@
         }
 
         // If APN is still enabled, try to bring it back up automatically
-        if (mAttached.get() && apnContext.isReady()
-                && retryAfterDisconnected(apnContext.getReason())) {
+        if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
             SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "false");
             // Wait a bit before trying the next APN, so that
             // we're not tying up the RIL command channel.
@@ -1774,6 +1827,9 @@
         } else {
             apnContext.setApnSetting(null);
             apnContext.setDataConnectionAc(null);
+            if (isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology())) {
+                setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION);
+            }
         }
     }
 
@@ -2183,7 +2239,16 @@
                         cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED);
                         mReregisterOnReconnectFailure = false;
                     }
-                    trySetupData(Phone.REASON_PS_RESTRICT_ENABLED, PhoneConstants.APN_TYPE_DEFAULT);
+                    ApnContext apnContext = mApnContexts.get(PhoneConstants.APN_TYPE_DEFAULT);
+                    if (apnContext != null) {
+                        apnContext.setReason(Phone.REASON_PS_RESTRICT_ENABLED);
+                        trySetupData(apnContext);
+                    } else {
+                        loge("**** Default ApnContext not found ****");
+                        if (Build.IS_DEBUGGABLE) {
+                            throw new RuntimeException("Default ApnContext not found");
+                        }
+                    }
                 }
                 break;
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
index 9e7702e..4a4433b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
@@ -63,12 +63,14 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.PriorityQueue;
 
 /**
  * {@hide}
@@ -251,9 +253,19 @@
                                     new HashMap<String, Integer>();
 
     /** Phone.APN_TYPE_* ===> ApnContext */
-    protected ConcurrentHashMap<String, ApnContext> mApnContexts =
+    protected final ConcurrentHashMap<String, ApnContext> mApnContexts =
                                     new ConcurrentHashMap<String, ApnContext>();
 
+    /** kept in sync with mApnContexts
+     * Higher numbers are higher priority and sorted so highest priority is first */
+    protected final PriorityQueue<ApnContext>mPrioritySortedApnContexts =
+            new PriorityQueue<ApnContext>(5,
+            new Comparator<ApnContext>() {
+                public int compare(ApnContext c1, ApnContext c2) {
+                    return c2.priority - c1.priority;
+                }
+            } );
+
     /* Currently active APN */
     protected ApnSetting mActiveApn;