Handle failure to pull an external call.

Look for two expected call fail causes which can occur if an external
call is being pulled, and the pulled ImsPhoneConnection disconnects.
These call fail causes are a signal that the call pull has failed.

We use this to trigger a callback which causes the TelephonyConnection to
swap the ImsPhoneConnection out for the ImsExternalConnection.

Bug: 29906222
Change-Id: I22e570ffd578ef5e474d6abaf5d89378bd17996c
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 3ba791d..d4e53d7 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -98,6 +98,7 @@
         public void onConferenceMergedFailed();
         public void onExtrasChanged(Bundle extras);
         public void onExitedEcmMode();
+        public void onCallPullFailed(Connection externalConnection);
     }
 
     /**
@@ -127,6 +128,8 @@
         public void onExtrasChanged(Bundle extras) {}
         @Override
         public void onExitedEcmMode() {}
+        @Override
+        public void onCallPullFailed(Connection externalConnection) {}
     }
 
     public static final int AUDIO_QUALITY_STANDARD = 1;
@@ -194,6 +197,13 @@
      */
     private boolean mIsPulledCall = false;
 
+    /**
+     * Where {@link #mIsPulledCall} is {@code true}, contains the dialog Id of the external call
+     * which is being pulled (e.g.
+     * {@link com.android.internal.telephony.imsphone.ImsExternalConnection#getCallId()}).
+     */
+    private int mPulledDialogId;
+
     protected Connection(int phoneType) {
         mPhoneType = phoneType;
     }
@@ -828,6 +838,21 @@
     }
 
     /**
+     * For an external call which is being pulled (e.g. {@link #isPulledCall()} is {@code true}),
+     * sets the dialog Id for the external call.  Used to handle failures to pull a call so that the
+     * pulled call can be reconciled with its original external connection.
+     *
+     * @param pulledDialogId The dialog id associated with a pulled call.
+     */
+    public void setPulledDialogId(int pulledDialogId) {
+        mPulledDialogId = pulledDialogId;
+    }
+
+    public int getPulledDialogId() {
+        return mPulledDialogId;
+    }
+
+    /**
      * Sets the call substate for the current connection and reports the changes to all listeners.
      * Valid call substates are defined in {@link android.telecom.Connection}.
      *
@@ -900,6 +925,20 @@
     }
 
     /**
+     * Notifies the connection that a call to {@link #pullExternalCall()} has failed to pull the
+     * call to the local device.
+     *
+     * @param externalConnection The original
+     *      {@link com.android.internal.telephony.imsphone.ImsExternalConnection} from which the
+     *      pull was initiated.
+     */
+    public void onCallPullFailed(Connection externalConnection) {
+        for (Listener l : mListeners) {
+            l.onCallPullFailed(externalConnection);
+        }
+    }
+
+    /**
      * Notifies this Connection of a request to disconnect a participant of the conference managed
      * by the connection.
      *
@@ -931,6 +970,8 @@
         StringBuilder str = new StringBuilder(128);
 
         str.append(" callId: " + getTelecomCallId());
+        str.append(" isExternal: " + (((mConnectionCapabilities & Capability.IS_EXTERNAL_CONNECTION)
+                == Capability.IS_EXTERNAL_CONNECTION) ? "Y" : "N"));
         if (Rlog.isLoggable(LOG_TAG, Log.DEBUG)) {
             str.append("addr: " + getAddress())
                     .append(" pres.: " + getNumberPresentation())
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsExternalCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsExternalCallTracker.java
index 7d7c047..4bea73d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsExternalCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsExternalCallTracker.java
@@ -88,7 +88,8 @@
                 Log.e(TAG, "onPullExternalCall : No call puller defined");
                 return;
             }
-            mCallPuller.pullExternalCall(connection.getAddress(), connection.getVideoState());
+            mCallPuller.pullExternalCall(connection.getAddress(), connection.getVideoState(),
+                    connection.getCallId());
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 89465a3..b2165e0 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -609,7 +609,10 @@
                 if (intentExtras.containsKey(ImsCallProfile.EXTRA_IS_CALL_PULL)) {
                     profile.mCallExtras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL,
                             intentExtras.getBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL));
+                    int dialogId = intentExtras.getInt(
+                            ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID);
                     conn.setIsPulledCall(true);
+                    conn.setPulledDialogId(dialogId);
                 }
 
                 // Pack the OEM-specific call extras.
@@ -1455,17 +1458,33 @@
                 mOnHoldToneStarted = false;
                 mOnHoldToneId = -1;
             }
-            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0
-                    && cause != DisconnectCause.ANSWERED_ELSEWHERE) {
-                // Missed
-                if (cause == DisconnectCause.NORMAL) {
-                    cause = DisconnectCause.INCOMING_MISSED;
-                } else {
-                    cause = DisconnectCause.INCOMING_REJECTED;
-                }
-                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
-                        + cause);
+            if (conn != null) {
+                if (conn.isPulledCall() && (
+                        reasonInfo.getCode() == ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC ||
+                        reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE) &&
+                        mPhone != null && mPhone.getExternalCallTracker() != null) {
 
+                    log("Call pull failed.");
+                    // Call was being pulled, but the call pull has failed -- inform the associated
+                    // TelephonyConnection that the pull failed, and provide it with the original
+                    // external connection which was pulled so that it can be swapped back.
+                    conn.onCallPullFailed(mPhone.getExternalCallTracker()
+                            .getConnectionById(conn.getPulledDialogId()));
+                    // Do not mark as disconnected; the call will just change from being a regular
+                    // call to being an external call again.
+                    cause = DisconnectCause.NOT_DISCONNECTED;
+
+                } else if (conn.isIncoming() && conn.getConnectTime() == 0
+                        && cause != DisconnectCause.ANSWERED_ELSEWHERE) {
+                    // Missed
+                    if (cause == DisconnectCause.NORMAL) {
+                        cause = DisconnectCause.INCOMING_MISSED;
+                    } else {
+                        cause = DisconnectCause.INCOMING_REJECTED;
+                    }
+                    if (DBG) log("Incoming connection of 0 connect time detected - translated " +
+                            "cause = " + cause);
+                }
             }
 
             if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
@@ -2383,11 +2402,14 @@
      *
      * @param number The phone number of the call to be pulled.
      * @param videoState The desired video state of the pulled call.
+     * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog id associated with the
+     *                 call which is being pulled.
      */
     @Override
-    public void pullExternalCall(String number, int videoState) {
+    public void pullExternalCall(String number, int videoState, int dialogId) {
         Bundle extras = new Bundle();
         extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true);
+        extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, dialogId);
         try {
             Connection connection = dial(number, videoState, extras);
             mPhone.notifyUnknownConnection(connection);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPullCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPullCall.java
index 793fb0b..dbb4036 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPullCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPullCall.java
@@ -31,6 +31,8 @@
      *
      * @param number The phone number of the call to be pulled.
      * @param videoState The video state of the call to be pulled.
+     * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog Id associated with the
+     *                 call to be pulled.
      */
-    void pullExternalCall(String number, int videoState);
+    void pullExternalCall(String number, int videoState, int dialogId);
 }