Ignore obsolete disconnect_completed msgs.

Msg passing meant the disconnect_completed was getting processed after
a new connect request was received and acted on, so the disc_completed
would causes us to mark the state incorrectly and we'd get in trouble.

bug:22776917
Change-Id: I781c0619dade00554d368a649c3e43a32cf511dc
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 081e9b7..afc8ed6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -93,6 +93,12 @@
      */
     private boolean mConcurrentVoiceAndDataAllowed;
 
+    /**
+     * used to track a single connection request so disconnects can get ignored if
+     * obsolete.
+     */
+    private final AtomicInteger mConnectionGeneration = new AtomicInteger(0);
+
     public ApnContext(Context context, String apnType, String logTag, NetworkConfig config,
             DcTrackerBase tracker) {
         mContext = context;
@@ -392,6 +398,14 @@
         return result;
     }
 
+    public int incAndGetConnectionGeneration() {
+        return mConnectionGeneration.incrementAndGet();
+    }
+
+    public int getConnectionGeneration() {
+        return mConnectionGeneration.get();
+    }
+
     @Override
     public synchronized String toString() {
         // We don't print mDataConnection because its recursive.
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 8877bf5..2f31246 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -65,6 +65,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.net.InetAddress;
 import java.util.Collection;
+import java.util.HashMap;
 
 /**
  * {@hide}
@@ -127,15 +128,18 @@
         int mRilRat;
         boolean mRetryWhenSSChange;
         Message mOnCompletedMsg;
+        final int mConnectionGeneration;
 
         ConnectionParams(ApnContext apnContext, int initialMaxRetry, int profileId,
-                int rilRadioTechnology, boolean retryWhenSSChange, Message onCompletedMsg) {
+                int rilRadioTechnology, boolean retryWhenSSChange, Message onCompletedMsg,
+                int connectionGeneration) {
             mApnContext = apnContext;
             mInitialMaxRetry = initialMaxRetry;
             mProfileId = profileId;
             mRilRat = rilRadioTechnology;
             mRetryWhenSSChange = retryWhenSSChange;
             mOnCompletedMsg = onCompletedMsg;
+            mConnectionGeneration = connectionGeneration;
         }
 
         @Override
@@ -190,7 +194,7 @@
     //***** Package visible variables
     int mTag;
     int mCid;
-    List<ApnContext> mApnContexts = null;
+    HashMap<ApnContext, ConnectionParams> mApnContexts = null;
     PendingIntent mReconnectIntent = null;
     RetryManager mRetryManager = new RetryManager();
 
@@ -448,7 +452,7 @@
             addState(mDisconnectingErrorCreatingConnection, mDefaultState);
         setInitialState(mInactiveState);
 
-        mApnContexts = new ArrayList<ApnContext>();
+        mApnContexts = new HashMap<ApnContext, ConnectionParams>();
         if (DBG) log("DataConnection constructor X");
     }
 
@@ -614,10 +618,13 @@
     private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
         mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
                 mNetworkInfo.getExtraInfo());
-        for (ApnContext apnContext : mApnContexts) {
+        for (ConnectionParams cp : mApnContexts.values()) {
+            ApnContext apnContext = cp.mApnContext;
             if (apnContext == alreadySent) continue;
             if (reason != null) apnContext.setReason(reason);
-            Message msg = mDct.obtainMessage(event, apnContext);
+            Pair<ApnContext, Integer> pair =
+                    new Pair<ApnContext, Integer>(apnContext, cp.mConnectionGeneration);
+            Message msg = mDct.obtainMessage(event, pair);
             AsyncResult.forMessage(msg);
             msg.sendToTarget();
         }
@@ -1038,8 +1045,8 @@
         mConnectionParams = cp;
         mConnectionParams.mTag = mTag;
 
-        if (!mApnContexts.contains(apnContext)) {
-            mApnContexts.add(apnContext);
+        if (!mApnContexts.containsKey(apnContext)) {
+            mApnContexts.put(apnContext, cp);
         }
         configureRetry(mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT));
         mRetryManager.setRetryCount(0);
@@ -1541,7 +1548,8 @@
                 case EVENT_DISCONNECT: {
                     DisconnectParams dp = (DisconnectParams) msg.obj;
 
-                    if (mApnContexts.remove(dp.mApnContext) && mApnContexts.size() == 0) {
+                    if ((mApnContexts.remove(dp.mApnContext) != null) &&
+                            (mApnContexts.size() == 0)) {
                         if (DBG) {
                             log("DcRetryingState msg.what=EVENT_DISCONNECT " + " RefCount="
                                     + mApnContexts.size() + " dp=" + dp);
@@ -1839,10 +1847,10 @@
                     if (DBG) {
                         log("DcActiveState: EVENT_CONNECT cp=" + cp + " dc=" + DataConnection.this);
                     }
-                    if (mApnContexts.contains(cp.mApnContext)) {
+                    if (mApnContexts.containsKey(cp.mApnContext)) {
                         log("DcActiveState ERROR already added apnContext=" + cp.mApnContext);
                     } else {
-                        mApnContexts.add(cp.mApnContext);
+                        mApnContexts.put(cp.mApnContext, cp);
                         if (DBG) {
                             log("DcActiveState msg.what=EVENT_CONNECT RefCount="
                                     + mApnContexts.size());
@@ -1858,7 +1866,7 @@
                         log("DcActiveState: EVENT_DISCONNECT dp=" + dp
                                 + " dc=" + DataConnection.this);
                     }
-                    if (mApnContexts.contains(dp.mApnContext)) {
+                    if (mApnContexts.containsKey(dp.mApnContext)) {
                         if (DBG) {
                             log("DcActiveState msg.what=EVENT_DISCONNECT RefCount="
                                     + mApnContexts.size());
@@ -2085,7 +2093,7 @@
             }
             // this can only happen if our exit has been called - we're already disconnected
             if (mApnContexts == null) return;
-            for (ApnContext apnContext : mApnContexts) {
+            for (ApnContext apnContext : mApnContexts.keySet()) {
                 log("DcNetworkAgent: [unwanted]: disconnect apnContext=" + apnContext);
                 Message msg = mDct.obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, apnContext);
                 DisconnectParams dp = new DisconnectParams(apnContext, apnContext.getReason(), msg);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
index b503a0d..d46646b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
@@ -368,14 +368,16 @@
      *        AsyncResult.result = FailCause and AsyncResult.exception = Exception().
      */
     public void bringUp(ApnContext apnContext, int initialMaxRetry, int profileId,
-            int rilRadioTechnology, boolean retryWhenSSChange, Message onCompletedMsg) {
+            int rilRadioTechnology, boolean retryWhenSSChange, Message onCompletedMsg,
+            int connectionGeneration) {
         if (DBG) {
             log("bringUp: apnContext=" + apnContext + " initialMaxRetry=" + initialMaxRetry
                 + " onCompletedMsg=" + onCompletedMsg);
         }
         sendMessage(DataConnection.EVENT_CONNECT,
                     new ConnectionParams(apnContext, initialMaxRetry, profileId,
-                            rilRadioTechnology, retryWhenSSChange, onCompletedMsg));
+                            rilRadioTechnology, retryWhenSSChange, onCompletedMsg,
+                            connectionGeneration));
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index 98d1c3b..61736ed 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -265,7 +265,7 @@
                             + " newState=" + newState.toString());
                     if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
                         if (mDct.mIsCleanupRequired) {
-                            apnsToCleanup.addAll(dc.mApnContexts);
+                            apnsToCleanup.addAll(dc.mApnContexts.keySet());
                             mDct.mIsCleanupRequired = false;
                         } else {
                             DcFailCause failCause = DcFailCause.fromInt(newState.status);
@@ -275,7 +275,7 @@
                                 mDct.sendRestartRadio();
                             } else if (mDct.isPermanentFail(failCause)) {
                                 if (DBG) log("onDataStateChanged: inactive, add to cleanup list");
-                                apnsToCleanup.addAll(dc.mApnContexts);
+                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
                             } else {
                                 if (DBG) log("onDataStateChanged: inactive, add to retry list");
                                 dcsToRetry.add(dc);
@@ -318,11 +318,11 @@
                                                     " oldLp=" + result.oldLp +
                                                     " newLp=" + result.newLp);
                                         }
-                                        apnsToCleanup.addAll(dc.mApnContexts);
+                                        apnsToCleanup.addAll(dc.mApnContexts.keySet());
                                     } else {
                                         if (DBG) log("onDataStateChanged: simple change");
 
-                                        for (ApnContext apnContext : dc.mApnContexts) {
+                                        for (ApnContext apnContext : dc.mApnContexts.keySet()) {
                                              mPhone.notifyDataConnection(
                                                  PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
                                                  apnContext.getApnType());
@@ -334,7 +334,7 @@
                                     }
                                 }
                             } else {
-                                apnsToCleanup.addAll(dc.mApnContexts);
+                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
                                 if (DBG) {
                                     log("onDataStateChanged: interface change, cleanup apns="
                                             + dc.mApnContexts);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 0a453b0..de6517f 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -57,6 +57,7 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.LocalLog;
+import android.util.Pair;
 import android.view.WindowManager;
 import android.telephony.Rlog;
 
@@ -1307,6 +1308,7 @@
 
         apnContext.setDataConnectionAc(dcac);
         apnContext.setApnSetting(apnSetting);
+        int connectionGeneration = apnContext.incAndGetConnectionGeneration();
         apnContext.setState(DctConstants.State.CONNECTING);
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
 
@@ -1314,7 +1316,7 @@
         msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
         msg.obj = apnContext;
         dcac.bringUp(apnContext, getInitialMaxRetry(), profileId, radioTech,
-                mAutoAttachOnCreation.get(), msg);
+                mAutoAttachOnCreation.get(), msg, connectionGeneration);
 
         if (DBG) log("setupData: initing!");
         return true;
@@ -1817,13 +1819,9 @@
 
         DcFailCause cause = DcFailCause.UNKNOWN;
         boolean handleError = false;
-        ApnContext apnContext = null;
+        ApnContext apnContext = getValidApnContext(ar, "onDataSetupComplete");
 
-        if(ar.userObj instanceof ApnContext){
-            apnContext = (ApnContext)ar.userObj;
-        } else {
-            throw new RuntimeException("onDataSetupComplete: No apnContext");
-        }
+        if (apnContext == null) return;
 
         if (ar.exception == null) {
             DcAsyncChannel dcac = apnContext.getDcAc();
@@ -2007,6 +2005,27 @@
     }
 
     /**
+     * check for obsolete messages.  Return ApnContext if valid, null if not
+     */
+    private ApnContext getValidApnContext(AsyncResult ar, String logString) {
+        if (ar != null && ar.userObj instanceof Pair) {
+            Pair<ApnContext, Integer>pair = (Pair<ApnContext, Integer>)ar.userObj;
+            ApnContext apnContext = pair.first;
+            if (apnContext != null) {
+                if (apnContext.getConnectionGeneration() == pair.second) {
+                    return apnContext;
+                } else {
+                    log("ignoring obsolete " + logString);
+                    return null;
+                }
+            }
+        }
+        throw new RuntimeException(logString + ": No apnContext");
+    }
+
+
+
+    /**
      * Error has occurred during the SETUP {aka bringUP} request and the DCT
      * should either try the next waiting APN or start over from the
      * beginning if the list is empty. Between each SETUP request there will
@@ -2015,13 +2034,9 @@
     @Override
     protected void onDataSetupCompleteError(AsyncResult ar) {
         String reason = "";
-        ApnContext apnContext = null;
+        ApnContext apnContext = getValidApnContext(ar, "onDataSetupCompleteError");
 
-        if(ar.userObj instanceof ApnContext){
-            apnContext = (ApnContext)ar.userObj;
-        } else {
-            throw new RuntimeException("onDataSetupCompleteError: No apnContext");
-        }
+        if (apnContext == null) return;
 
         // See if there are more APN's to try
         if (apnContext.getWaitingApns().isEmpty()) {
@@ -2055,15 +2070,9 @@
      * Called when EVENT_DISCONNECT_DONE is received.
      */
     @Override
-    protected void onDisconnectDone(int connId, AsyncResult ar) {
-        ApnContext apnContext = null;
-
-        if (ar.userObj instanceof ApnContext) {
-            apnContext = (ApnContext) ar.userObj;
-        } else {
-            loge("onDisconnectDone: Invalid ar in onDisconnectDone, ignore");
-            return;
-        }
+    protected void onDisconnectDone(AsyncResult ar) {
+        ApnContext apnContext = getValidApnContext(ar, "onDisconnectDone");
+        if (apnContext == null) return;
 
         if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE apnContext=" + apnContext);
         apnContext.setState(DctConstants.State.IDLE);
@@ -2134,16 +2143,10 @@
      * Called when EVENT_DISCONNECT_DC_RETRYING is received.
      */
     @Override
-    protected void onDisconnectDcRetrying(int connId, AsyncResult ar) {
+    protected void onDisconnectDcRetrying(AsyncResult ar) {
         // We could just do this in DC!!!
-        ApnContext apnContext = null;
-
-        if (ar.userObj instanceof ApnContext) {
-            apnContext = (ApnContext) ar.userObj;
-        } else {
-            loge("onDisconnectDcRetrying: Invalid ar in onDisconnectDone, ignore");
-            return;
-        }
+        ApnContext apnContext = getValidApnContext(ar, "onDisconnectDcRetrying");
+        if (apnContext == null) return;
 
         apnContext.setState(DctConstants.State.RETRYING);
         if(DBG) log("onDisconnectDcRetrying: apnContext=" + apnContext);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
index 5c0226e..d2fc619 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
@@ -231,9 +231,6 @@
     /** Intent sent when the reconnect alarm fires. */
     protected PendingIntent mReconnectIntent = null;
 
-    /** CID of active data connection */
-    protected int mCidActive;
-
     // When false we will not auto attach and manually attaching is required.
     protected boolean mAutoAttachOnCreationConfig = false;
     protected AtomicBoolean mAutoAttachOnCreation = new AtomicBoolean(false);
@@ -876,8 +873,8 @@
     protected abstract void onRadioOffOrNotAvailable();
     protected abstract void onDataSetupComplete(AsyncResult ar);
     protected abstract void onDataSetupCompleteError(AsyncResult ar);
-    protected abstract void onDisconnectDone(int connId, AsyncResult ar);
-    protected abstract void onDisconnectDcRetrying(int connId, AsyncResult ar);
+    protected abstract void onDisconnectDone(AsyncResult ar);
+    protected abstract void onDisconnectDcRetrying(AsyncResult ar);
     protected abstract void onVoiceCallStarted();
     protected abstract void onVoiceCallEnded();
     protected abstract void onCleanUpConnection(boolean tearDown, int apnId, String reason);
@@ -933,7 +930,6 @@
                 break;
 
             case DctConstants.EVENT_DATA_SETUP_COMPLETE:
-                mCidActive = msg.arg1;
                 onDataSetupComplete((AsyncResult) msg.obj);
                 break;
 
@@ -943,12 +939,12 @@
 
             case DctConstants.EVENT_DISCONNECT_DONE:
                 log("DataConnectionTracker.handleMessage: EVENT_DISCONNECT_DONE msg=" + msg);
-                onDisconnectDone(msg.arg1, (AsyncResult) msg.obj);
+                onDisconnectDone((AsyncResult) msg.obj);
                 break;
 
             case DctConstants.EVENT_DISCONNECT_DC_RETRYING:
                 log("DataConnectionTracker.handleMessage: EVENT_DISCONNECT_DC_RETRYING msg=" + msg);
-                onDisconnectDcRetrying(msg.arg1, (AsyncResult) msg.obj);
+                onDisconnectDcRetrying((AsyncResult) msg.obj);
                 break;
 
             case DctConstants.EVENT_VOICE_CALL_STARTED:
@@ -1947,7 +1943,6 @@
         pw.println(" mResolver=" + mResolver);
         pw.println(" mIsWifiConnected=" + mIsWifiConnected);
         pw.println(" mReconnectIntent=" + mReconnectIntent);
-        pw.println(" mCidActive=" + mCidActive);
         pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation.get());
         pw.println(" mIsScreenOn=" + mIsScreenOn);
         pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);