Merge "Fixed the aggresive data call setup"
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 53d4cb5..3fd30a9 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -1659,7 +1659,13 @@
 
                 // Setting SS Roaming (general)
                 if (mIsSubscriptionFromRuim) {
-                    mNewSS.setVoiceRoaming(isRoamingBetweenOperators(mNewSS.getVoiceRoaming(), mNewSS));
+                    boolean isRoamingBetweenOperators = isRoamingBetweenOperators(
+                            mNewSS.getVoiceRoaming(), mNewSS);
+                    if (isRoamingBetweenOperators != mNewSS.getVoiceRoaming()) {
+                        log("isRoamingBetweenOperators=" + isRoamingBetweenOperators
+                                + ". Override CDMA voice roaming to " + isRoamingBetweenOperators);
+                        mNewSS.setVoiceRoaming(isRoamingBetweenOperators);
+                    }
                 }
                 /**
                  * For CDMA, voice and data should have the same roaming status.
@@ -1672,15 +1678,25 @@
                     final boolean isVoiceInService =
                             (mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
                     if (isVoiceInService) {
-                        mNewSS.setDataRoaming(mNewSS.getVoiceRoaming());
+                        boolean isVoiceRoaming = mNewSS.getVoiceRoaming();
+                        if (mNewSS.getDataRoaming() != isVoiceRoaming) {
+                            log("Data roaming != Voice roaming. Override data roaming to "
+                                    + isVoiceRoaming);
+                            mNewSS.setDataRoaming(isVoiceRoaming);
+                        }
                     } else {
                         /**
                          * As per VoiceRegStateResult from radio types.hal the TSB58
                          * Roaming Indicator shall be sent if device is registered
                          * on a CDMA or EVDO system.
                          */
-                        mNewSS.setDataRoaming(
-                                !isRoamIndForHomeSystem(Integer.toString(mRoamingIndicator)));
+                        boolean isRoamIndForHomeSystem = isRoamIndForHomeSystem(
+                                Integer.toString(mRoamingIndicator));
+                        if (mNewSS.getDataRoaming() == isRoamIndForHomeSystem) {
+                            log("isRoamIndForHomeSystem=" + isRoamIndForHomeSystem
+                                    + ", override data roaming to " + !isRoamIndForHomeSystem);
+                            mNewSS.setDataRoaming(!isRoamIndForHomeSystem);
+                        }
                     }
                 }
 
@@ -1885,6 +1901,9 @@
                     mNewReasonDataDenied = dataRegStateResult.reasonDataDenied;
                     mNewMaxDataCalls = dataRegStateResult.maxDataCalls;
                     mDataRoaming = regCodeIsRoaming(regState);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(mDataRoaming);
 
                     if (DBG) {
                         log("handlPollStateResultMessage: GsmSST setDataRegState=" + dataRegState
@@ -1893,7 +1912,11 @@
                     }
                 } else if (mPhone.isPhoneTypeCdma()) {
 
-                    mNewSS.setDataRoaming(regCodeIsRoaming(regState));
+                    boolean isDataRoaming = regCodeIsRoaming(regState);
+                    mNewSS.setDataRoaming(isDataRoaming);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
 
                     if (DBG) {
                         log("handlPollStateResultMessage: cdma setDataRegState=" + dataRegState
@@ -1918,11 +1941,15 @@
                     }
 
                     // voice roaming state in done while handling EVENT_POLL_STATE_REGISTRATION_CDMA
-                    mNewSS.setDataRoaming(regCodeIsRoaming(regState));
+                    boolean isDataRoaming = regCodeIsRoaming(regState);
+                    mNewSS.setDataRoaming(isDataRoaming);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                     if (DBG) {
-                        log("handlPollStateResultMessage: CdmaLteSST setDataRegState=" + dataRegState
-                                + " regState=" + regState
-                                + " dataRadioTechnology=" + newDataRat);
+                        log("handlPollStateResultMessage: CdmaLteSST setDataRegState="
+                                + dataRegState + " regState=" + regState + " dataRadioTechnology="
+                                + newDataRat);
                     }
                 }
 
@@ -2012,10 +2039,9 @@
      */
     private boolean isRoamIndForHomeSystem(String roamInd) {
         // retrieve the carrier-specified list of ERIs for home system
-        log("isRoamIndForHomeSystem: " + Resources.getSystem()
-                .getConfiguration().toString());
         String[] homeRoamIndicators = Resources.getSystem()
                 .getStringArray(com.android.internal.R.array.config_cdma_home_system);
+        log("isRoamIndForHomeSystem: homeRoamIndicators=" + Arrays.toString(homeRoamIndicators));
 
         if (homeRoamIndicators != null) {
             // searches through the comma-separated list for a match,
@@ -2056,10 +2082,6 @@
              */
             boolean roaming = (mGsmRoaming || mDataRoaming);
 
-            // Save the data roaming state reported by modem registration before resource overlay or
-            // carrier config possibly overrides it.
-            mNewSS.setDataRoamingFromRegistration(roaming);
-
             if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS)
                     && (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) {
                 log("updateRoamingState: resource override set non roaming.isSameNamedOperators="
@@ -2097,9 +2119,6 @@
             mNewSS.setVoiceRoaming(roaming);
             mNewSS.setDataRoaming(roaming);
         } else {
-            // Save the roaming state before carrier config possibly overrides it.
-            mNewSS.setDataRoamingFromRegistration(mNewSS.getDataRoaming());
-
             CarrierConfigManager configLoader = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configLoader != null) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java b/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
index 0ce1991..ce8318d 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
@@ -24,6 +24,7 @@
 import android.telephony.ServiceState;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
@@ -33,6 +34,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class represents a apn setting for create PDP link
@@ -42,6 +44,7 @@
     static final String LOG_TAG = "ApnSetting";
 
     private static final boolean DBG = false;
+    private static final boolean VDBG = false;
 
     static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
     static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
@@ -544,6 +547,116 @@
                 && mvnoMatchData.equals(other.mvnoMatchData);
     }
 
+    /**
+     * Compare two APN settings
+     *
+     * Note: This method does not compare 'id', 'bearer', 'bearerBitmask'. We only use this for
+     * determining if tearing a data call is needed when conditions change. See
+     * cleanUpConnectionsOnUpdatedApns in DcTracker.
+     *
+     * @param o the other object to compare
+     * @param isDataRoaming True if the device is on data roaming
+     * @return True if the two APN settings are same
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean equals(Object o, boolean isDataRoaming) {
+        if (!(o instanceof ApnSetting)) {
+            return false;
+        }
+
+        ApnSetting other = (ApnSetting) o;
+
+        return carrier.equals(other.carrier)
+                && numeric.equals(other.numeric)
+                && apn.equals(other.apn)
+                && proxy.equals(other.proxy)
+                && mmsc.equals(other.mmsc)
+                && mmsProxy.equals(other.mmsProxy)
+                && TextUtils.equals(mmsPort, other.mmsPort)
+                && port.equals(other.port)
+                && TextUtils.equals(user, other.user)
+                && TextUtils.equals(password, other.password)
+                && authType == other.authType
+                && Arrays.deepEquals(types, other.types)
+                && typesBitmap == other.typesBitmap
+                && (isDataRoaming || protocol.equals(other.protocol))
+                && (!isDataRoaming || roamingProtocol.equals(other.roamingProtocol))
+                && carrierEnabled == other.carrierEnabled
+                && profileId == other.profileId
+                && modemCognitive == other.modemCognitive
+                && maxConns == other.maxConns
+                && waitTime == other.waitTime
+                && maxConnsTime == other.maxConnsTime
+                && mtu == other.mtu
+                && mvnoType.equals(other.mvnoType)
+                && mvnoMatchData.equals(other.mvnoMatchData);
+    }
+
+    /**
+     * Check if neither mention DUN and are substantially similar
+     *
+     * @param other The other APN settings to compare
+     * @return True if two APN settings are similar
+     */
+    public boolean similar(ApnSetting other) {
+        return (!this.canHandleType(PhoneConstants.APN_TYPE_DUN)
+                && !other.canHandleType(PhoneConstants.APN_TYPE_DUN)
+                && Objects.equals(this.apn, other.apn)
+                && !typeSameAny(this, other)
+                && xorEquals(this.proxy, other.proxy)
+                && xorEquals(this.port, other.port)
+                && xorEquals(this.protocol, other.protocol)
+                && xorEquals(this.roamingProtocol, other.roamingProtocol)
+                && this.carrierEnabled == other.carrierEnabled
+                && this.bearerBitmask == other.bearerBitmask
+                && this.profileId == other.profileId
+                && Objects.equals(this.mvnoType, other.mvnoType)
+                && Objects.equals(this.mvnoMatchData, other.mvnoMatchData)
+                && xorEquals(this.mmsc, other.mmsc)
+                && xorEquals(this.mmsProxy, other.mmsProxy)
+                && xorEquals(this.mmsPort, other.mmsPort));
+    }
+
+    // check whether the types of two APN same (even only one type of each APN is same)
+    private boolean typeSameAny(ApnSetting first, ApnSetting second) {
+        if (VDBG) {
+            StringBuilder apnType1 = new StringBuilder(first.apn + ": ");
+            for (int index1 = 0; index1 < first.types.length; index1++) {
+                apnType1.append(first.types[index1]);
+                apnType1.append(",");
+            }
+
+            StringBuilder apnType2 = new StringBuilder(second.apn + ": ");
+            for (int index1 = 0; index1 < second.types.length; index1++) {
+                apnType2.append(second.types[index1]);
+                apnType2.append(",");
+            }
+            Rlog.d(LOG_TAG, "APN1: is " + apnType1);
+            Rlog.d(LOG_TAG, "APN2: is " + apnType2);
+        }
+
+        for (int index1 = 0; index1 < first.types.length; index1++) {
+            for (int index2 = 0; index2 < second.types.length; index2++) {
+                if (first.types[index1].equals(PhoneConstants.APN_TYPE_ALL)
+                        || second.types[index2].equals(PhoneConstants.APN_TYPE_ALL)
+                        || first.types[index1].equals(second.types[index2])) {
+                    if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return true");
+                    return true;
+                }
+            }
+        }
+
+        if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return false");
+        return false;
+    }
+
+    // equal or one is not specified
+    private boolean xorEquals(String first, String second) {
+        return (Objects.equals(first, second)
+                || TextUtils.isEmpty(first)
+                || TextUtils.isEmpty(second));
+    }
+
     // Helper function to convert APN string into a 32-bit bitmask.
     private static int getApnBitmask(String apn) {
         switch (apn) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 6aaacd7..feb9fe0 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -500,21 +500,12 @@
                 discReason = RILConstants.DEACTIVATE_REASON_PDP_RESET;
             }
         }
-        if (mPhone.mCi.getRadioState().isOn()
-                || (mPhone.getServiceState().getRilDataRadioTechnology()
-                        == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN )) {
-            String str = "tearDownData radio is on, call deactivateDataCall";
-            if (DBG) log(str);
-            if (apnContext != null) apnContext.requestLog(str);
-            mPhone.mCi.deactivateDataCall(mCid, discReason,
-                    obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o));
-        } else {
-            String str = "tearDownData radio is off sendMessage EVENT_DEACTIVATE_DONE immediately";
-            if (DBG) log(str);
-            if (apnContext != null) apnContext.requestLog(str);
-            AsyncResult ar = new AsyncResult(o, null, null);
-            sendMessage(obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, ar));
-        }
+
+        String str = "tearDownData. mCid=" + mCid + ", reason=" + discReason;
+        if (DBG) log(str);
+        if (apnContext != null) apnContext.requestLog(str);
+        mPhone.mCi.deactivateDataCall(mCid, discReason,
+                obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o));
     }
 
     private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index ffffe97..b8ebd1a 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -101,7 +101,6 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map.Entry;
-import java.util.Objects;
 import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -3371,7 +3370,7 @@
             int j = i + 1;
             while (j < mAllApnSettings.size()) {
                 second = mAllApnSettings.get(j);
-                if (apnsSimilar(first, second)) {
+                if (first.similar(second)) {
                     ApnSetting newApn = mergeApns(first, second);
                     mAllApnSettings.set(i, newApn);
                     first = newApn;
@@ -3384,66 +3383,6 @@
         }
     }
 
-    //check whether the types of two APN same (even only one type of each APN is same)
-    private boolean apnTypeSameAny(ApnSetting first, ApnSetting second) {
-        if(VDBG) {
-            StringBuilder apnType1 = new StringBuilder(first.apn + ": ");
-            for(int index1 = 0; index1 < first.types.length; index1++) {
-                apnType1.append(first.types[index1]);
-                apnType1.append(",");
-            }
-
-            StringBuilder apnType2 = new StringBuilder(second.apn + ": ");
-            for(int index1 = 0; index1 < second.types.length; index1++) {
-                apnType2.append(second.types[index1]);
-                apnType2.append(",");
-            }
-            log("APN1: is " + apnType1);
-            log("APN2: is " + apnType2);
-        }
-
-        for(int index1 = 0; index1 < first.types.length; index1++) {
-            for(int index2 = 0; index2 < second.types.length; index2++) {
-                if(first.types[index1].equals(PhoneConstants.APN_TYPE_ALL) ||
-                        second.types[index2].equals(PhoneConstants.APN_TYPE_ALL) ||
-                        first.types[index1].equals(second.types[index2])) {
-                    if(VDBG)log("apnTypeSameAny: return true");
-                    return true;
-                }
-            }
-        }
-
-        if(VDBG)log("apnTypeSameAny: return false");
-        return false;
-    }
-
-    // Check if neither mention DUN and are substantially similar
-    private boolean apnsSimilar(ApnSetting first, ApnSetting second) {
-        return (!first.canHandleType(PhoneConstants.APN_TYPE_DUN)
-                && !second.canHandleType(PhoneConstants.APN_TYPE_DUN)
-                && Objects.equals(first.apn, second.apn)
-                && !apnTypeSameAny(first, second)
-                && xorEquals(first.proxy, second.proxy)
-                && xorEquals(first.port, second.port)
-                && xorEquals(first.protocol, second.protocol)
-                && xorEquals(first.roamingProtocol, second.roamingProtocol)
-                && first.carrierEnabled == second.carrierEnabled
-                && first.bearerBitmask == second.bearerBitmask
-                && first.profileId == second.profileId
-                && Objects.equals(first.mvnoType, second.mvnoType)
-                && Objects.equals(first.mvnoMatchData, second.mvnoMatchData)
-                && xorEquals(first.mmsc, second.mmsc)
-                && xorEquals(first.mmsProxy, second.mmsProxy)
-                && xorEquals(first.mmsPort, second.mmsPort));
-    }
-
-    // equal or one is not specified
-    private boolean xorEquals(String first, String second) {
-        return (Objects.equals(first, second) ||
-                TextUtils.isEmpty(first) ||
-                TextUtils.isEmpty(second));
-    }
-
     private ApnSetting mergeApns(ApnSetting dest, ApnSetting src) {
         int id = dest.id;
         ArrayList<String> resultTypes = new ArrayList<String>();
@@ -4382,6 +4321,23 @@
         }
     }
 
+    private boolean containsAllApns(ArrayList<ApnSetting> oldApnList,
+                                    ArrayList<ApnSetting> newApnList) {
+        for (ApnSetting newApnSetting : newApnList) {
+            boolean canHandle = false;
+            for (ApnSetting oldApnSetting : oldApnList) {
+                // Make sure at least one of the APN from old list can cover the new APN
+                if (oldApnSetting.equals(newApnSetting,
+                        mPhone.getServiceState().getDataRoamingFromRegistration())) {
+                    canHandle = true;
+                    break;
+                }
+            }
+            if (!canHandle) return false;
+        }
+        return true;
+    }
+
     private void cleanUpConnectionsOnUpdatedApns(boolean tearDown, String reason) {
         if (DBG) log("cleanUpConnectionsOnUpdatedApns: tearDown=" + tearDown);
         if (mAllApnSettings != null && mAllApnSettings.isEmpty()) {
@@ -4395,7 +4351,10 @@
                 if (VDBG) log("new waitingApns:" + waitingApns);
                 if ((currentWaitingApns != null)
                         && ((waitingApns.size() != currentWaitingApns.size())
-                        || (!currentWaitingApns.containsAll(waitingApns)))) {
+                        // Check if the existing waiting APN list can cover the newly built APN
+                        // list. If yes, then we don't need to tear down the existing data call.
+                        // TODO: We probably need to rebuild APN list when roaming status changes.
+                        || !containsAllApns(currentWaitingApns, waitingApns))) {
                     if (VDBG) log("new waiting apn is different for " + apnContext);
                     apnContext.setWaitingApns(waitingApns);
                     if (!apnContext.isDisconnected()) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
index d22fe6f..3624ec1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
@@ -749,4 +749,67 @@
             }
         }
     }
-}
+
+    @Test
+    @SmallTest
+    public void testEqualsRoamingProtocol() throws Exception {
+        ApnSetting apn1 = new ApnSetting(
+                1234,
+                "310260",
+                "",
+                "ims",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                -1,
+                 new String[]{"ims"},
+                "IPV6",
+                "",
+                true,
+                0,
+                131071,
+                0,
+                false,
+                0,
+                0,
+                0,
+                1440,
+                "",
+                "");
+
+        ApnSetting apn2 = new ApnSetting(
+                1235,
+                "310260",
+                "",
+                "ims",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                -1,
+                new String[]{"ims"},
+                "IPV6",
+                "IPV6",
+                true,
+                0,
+                131072,
+                0,
+                false,
+                0,
+                0,
+                0,
+                1440,
+                "",
+                "");
+
+        assertTrue(apn1.equals(apn2, false));
+        assertFalse(apn1.equals(apn2, true));
+    }
+}
\ No newline at end of file