Add support for adding external calls to Telecom.

- Modify PstnIncomingCallNotifier to include the external call ID in the
"addNewUnknownCall" request to Telecom; this ensures we can key back in on
the ID when Telephony is asked to add that connection.
- Modify TelephonyConnectionService so it can add new external calls.
- Update TelephonyConnection to translate the new internal connection
properties related to multi-endpoint to ones Telecom can relate to.

Bug: 27458894
Change-Id: I47ad22aaeed955bcecbae894486c00539f8f7ea7
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 91d75c4..cfcf466 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -29,6 +29,8 @@
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
+import com.android.internal.telephony.imsphone.ImsExternalConnection;
 import com.android.phone.PhoneUtils;
 
 import com.google.common.base.Preconditions;
@@ -177,6 +179,18 @@
                 Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null);
                 extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, uri);
             }
+            // ImsExternalConnections are keyed by a unique mCallId; include this as an extra on
+            // the call to addNewUknownCall in Telecom.  This way when the request comes back to the
+            // TelephonyConnectionService, we will be able to determine which unknown connection is
+            // being added.
+            if (connection instanceof ImsExternalConnection) {
+                if (extras == null) {
+                    extras = new Bundle();
+                }
+                ImsExternalConnection externalConnection = (ImsExternalConnection) connection;
+                extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
+                        externalConnection.getCallId());
+            }
             TelecomManager.from(mPhone.getContext()).addNewUnknownCall(
                     PhoneUtils.makePstnPhoneAccountHandle(mPhone), extras);
         } else {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index ba20e74..8e67549 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -614,6 +614,8 @@
         newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
                 mIsVideoPauseSupported && isVideoCapable());
+        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL,
+                isExternalConnection() && isPullable());
         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
 
         if (getConnectionCapabilities() != newCapabilities) {
@@ -642,6 +644,8 @@
 
         newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, mHasHighDefAudio);
         newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi);
+        newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
+                isExternalConnection());
 
         if (getConnectionProperties() != newProperties) {
             setConnectionProperties(newProperties);
@@ -1100,6 +1104,33 @@
     }
 
     /**
+     * Determines if the current connection is an external connection.
+     *
+     * A connection is deemed to be external if the original connection capabilities state that it
+     * is.
+     *
+     * @return {@code true} if the connection is external, {@code false} otherwise.
+     */
+    private boolean isExternalConnection() {
+        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
+                && can(mOriginalConnectionCapabilities,
+                Capability.IS_EXTERNAL_CONNECTION);
+    }
+
+    /**
+     * Determines if the current connection is pullable.
+     *
+     * A connection is deemed to be pullable if the original connection capabilities state that it
+     * is.
+     *
+     * @return {@code true} if the connection is pullable, {@code false} otherwise.
+     */
+    private boolean isPullable() {
+        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
+                && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE);
+    }
+
+    /**
      * Applies capabilities specific to conferences termination to the
      * {@code ConnectionCapabilities} bit-mask.
      *
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index d93fb96..4844df9 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Bundle;
 import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -45,6 +46,8 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
+import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.phone.MMIDialogActivity;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
@@ -396,26 +399,51 @@
                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
                             "Phone is null"));
         }
+        Bundle extras = request.getExtras();
 
         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
-        final Call ringingCall = phone.getRingingCall();
-        if (ringingCall.hasConnections()) {
-            allConnections.addAll(ringingCall.getConnections());
-        }
-        final Call foregroundCall = phone.getForegroundCall();
-        if ((foregroundCall.getState() != Call.State.DISCONNECTED)
-                && (foregroundCall.hasConnections())) {
-            allConnections.addAll(foregroundCall.getConnections());
-        }
-        if (phone.getImsPhone() != null) {
-            final Call imsFgCall = phone.getImsPhone().getForegroundCall();
-            if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall.hasConnections()) {
-                allConnections.addAll(imsFgCall.getConnections());
+
+        // Handle the case where an unknown connection has an IMS external call ID specified; we can
+        // skip the rest of the guesswork and just grad that unknown call now.
+        if (phone.getImsPhone() != null && extras != null &&
+                extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
+
+            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+            ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
+            int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
+                    -1);
+
+            if (externalCallTracker != null) {
+                com.android.internal.telephony.Connection connection =
+                        externalCallTracker.getConnectionById(externalCallId);
+
+                if (connection != null) {
+                    allConnections.add(connection);
+                }
             }
         }
-        final Call backgroundCall = phone.getBackgroundCall();
-        if (backgroundCall.hasConnections()) {
-            allConnections.addAll(phone.getBackgroundCall().getConnections());
+
+        if (allConnections.isEmpty()) {
+            final Call ringingCall = phone.getRingingCall();
+            if (ringingCall.hasConnections()) {
+                allConnections.addAll(ringingCall.getConnections());
+            }
+            final Call foregroundCall = phone.getForegroundCall();
+            if ((foregroundCall.getState() != Call.State.DISCONNECTED)
+                    && (foregroundCall.hasConnections())) {
+                allConnections.addAll(foregroundCall.getConnections());
+            }
+            if (phone.getImsPhone() != null) {
+                final Call imsFgCall = phone.getImsPhone().getForegroundCall();
+                if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
+                        .hasConnections()) {
+                    allConnections.addAll(imsFgCall.getConnections());
+                }
+            }
+            final Call backgroundCall = phone.getBackgroundCall();
+            if (backgroundCall.hasConnections()) {
+                allConnections.addAll(phone.getBackgroundCall().getConnections());
+            }
         }
 
         com.android.internal.telephony.Connection unknownConnection = null;