Merge \"Finalize multiendpoint functionality.\" into nyc-mr1-dev
am: b489d6c2a1

Change-Id: Iaf8e0b5957036a7c62a10d77bed86814ea8a67a8
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 5ea199b..b649b90 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -344,7 +344,8 @@
             // state. We can assume that the active call will be automatically held which will
             // send another update at which point we will be in the right state.
             if (mCallsManager.getActiveCall() != null
-                    && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
+                    && oldState == CallState.CONNECTING &&
+                    (newState == CallState.DIALING || newState == CallState.PULLING)) {
                 return;
             }
             updateHeadsetWithCallState(false /* force */);
@@ -823,6 +824,7 @@
             case CallState.CONNECTING:
             case CallState.SELECT_PHONE_ACCOUNT:
             case CallState.DIALING:
+            case CallState.PULLING:
                 // Yes, this is correctly returning ALERTING.
                 // "Dialing" for BT means that we have sent information to the service provider
                 // to place the call but there is no confirmation that the call is going through.
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 0a967ac..1139cc5 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -677,6 +677,9 @@
                 case CallState.DIALING:
                     event = Log.Events.SET_DIALING;
                     break;
+                case CallState.PULLING:
+                    event = Log.Events.SET_PULLING;
+                    break;
                 case CallState.DISCONNECTED:
                     event = Log.Events.SET_DISCONNECTED;
                     data = getDisconnectCause();
@@ -2001,6 +2004,8 @@
                 return CallState.ACTIVE;
             case Connection.STATE_DIALING:
                 return CallState.DIALING;
+            case Connection.STATE_PULLING_CALL:
+                return CallState.PULLING;
             case Connection.STATE_DISCONNECTED:
                 return CallState.DISCONNECTED;
             case Connection.STATE_HOLDING:
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 0ff80f4..67003d8 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -72,6 +72,7 @@
             put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
             put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
             put(CallState.DIALING, mActiveDialingOrConnectingCalls);
+            put(CallState.PULLING, mActiveDialingOrConnectingCalls);
             put(CallState.RINGING, mRingingCalls);
             put(CallState.ON_HOLD, mHoldingCalls);
         }};
@@ -489,9 +490,13 @@
             case CallState.ON_HOLD:
                 onCallLeavingHold();
                 break;
+            case CallState.PULLING:
+                onCallLeavingActiveDialingOrConnecting();
+                break;
             case CallState.DIALING:
                 stopRingbackForCall(call);
                 onCallLeavingActiveDialingOrConnecting();
+                break;
         }
     }
 
@@ -507,6 +512,9 @@
             case CallState.ON_HOLD:
                 onCallEnteringHold();
                 break;
+            case CallState.PULLING:
+                onCallEnteringActiveDialingOrConnecting();
+                break;
             case CallState.DIALING:
                 onCallEnteringActiveDialingOrConnecting();
                 playRingbackForCall(call);
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 0d2ca48..0aa928f 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -103,6 +103,15 @@
      */
     public static final int DISCONNECTING = 9;
 
+    /**
+     * Indicates that the call is in the process of being pulled to the local device.
+     * <p>
+     * This state should only be set on a call with
+     * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+     * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL}.
+     */
+    public static final int PULLING = 10;
+
     public static String toString(int callState) {
         switch (callState) {
             case NEW:
@@ -125,6 +134,8 @@
                 return "ABORTED";
             case DISCONNECTING:
                 return "DISCONNECTING";
+            case PULLING:
+                return "PULLING";
             default:
                 return "UNKNOWN";
         }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index c13c8a2..d3b23c2 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -119,11 +119,13 @@
     private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
 
     private static final int[] OUTGOING_CALL_STATES =
-            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING};
+            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
+                    CallState.PULLING};
 
     private static final int[] LIVE_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
-                CallState.ACTIVE};
+                    CallState.PULLING, CallState.ACTIVE};
+
     public static final String TELECOM_CALL_ID_PREFIX = "TC@";
 
     // Maps call technologies in PhoneConstants to those in Analytics.
@@ -996,7 +998,8 @@
             // STATE_DIALING, put it on hold before answering the call.
             if (foregroundCall != null && foregroundCall != call &&
                     (foregroundCall.isActive() ||
-                     foregroundCall.getState() == CallState.DIALING)) {
+                     foregroundCall.getState() == CallState.DIALING ||
+                     foregroundCall.getState() == CallState.PULLING)) {
                 if (0 == (foregroundCall.getConnectionCapabilities()
                         & Connection.CAPABILITY_HOLD)) {
                     // This call does not support hold.  If it is from a different connection
@@ -1357,6 +1360,11 @@
         maybeMoveToSpeakerPhone(call);
     }
 
+    void markCallAsPulling(Call call) {
+        setCallState(call, CallState.PULLING, "pulling set explicitly");
+        maybeMoveToSpeakerPhone(call);
+    }
+
     void markCallAsActive(Call call) {
         setCallState(call, CallState.ACTIVE, "active set explicitly");
         maybeMoveToSpeakerPhone(call);
@@ -1450,7 +1458,8 @@
             } else if (HeadsetMediaButton.LONG_PRESS == type) {
                 Log.d(this, "handleHeadsetHook: longpress -> hangup");
                 Call callToHangup = getFirstCallWithState(
-                        CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
+                        CallState.RINGING, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
+                        CallState.ON_HOLD);
                 if (callToHangup != null) {
                     callToHangup.disconnect();
                     return true;
@@ -1480,6 +1489,9 @@
             if (call.isEmergencyCall()) {
                 // We never support add call if one of the calls is an emergency call.
                 return false;
+            } else if (call.isExternalCall()) {
+                // External calls don't count.
+                continue;
             } else if (call.getParentCall() == null) {
                 count++;
             }
@@ -1850,7 +1862,7 @@
     }
 
     private boolean hasMaximumDialingCalls() {
-        return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING);
+        return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING, CallState.PULLING);
     }
 
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index ca0e280..bf82a99 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -161,6 +161,24 @@
         }
 
         @Override
+        public void setPulling(String callId) {
+            Log.startSession(Log.Sessions.CSW_SET_PULLING);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("setPulling %s", callId);
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.markCallAsPulling(call);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void setDisconnected(String callId, DisconnectCause disconnectCause) {
             Log.startSession(Log.Sessions.CSW_SET_DISCONNECTED);
             long token = Binder.clearCallingIdentity();
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 69cc129..9bfeb7f 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -114,7 +114,8 @@
         int state = call.getState();
         return state == CallState.NEW
             || state == CallState.CONNECTING
-            || state == CallState.DIALING;
+            || state == CallState.DIALING
+            || state == CallState.PULLING;
     }
 
     private long getTimeoutLengthMillis() {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index e0a9def..1432373 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -630,6 +630,7 @@
             // Track the call if we don't already know about it.
             addCall(call);
 
+            List<ComponentName> componentsUpdated = new ArrayList<>();
             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
                 InCallServiceInfo info = entry.getKey();
 
@@ -637,15 +638,18 @@
                     continue;
                 }
 
+                componentsUpdated.add(info.getComponentName());
                 IInCallService inCallService = entry.getValue();
 
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
-                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar());
+                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
+                        info.isExternalCallsSupported());
                 try {
                     inCallService.addCall(parcelableCall);
                 } catch (RemoteException ignored) {
                 }
             }
+            Log.i(this, "Call added to components: %s", componentsUpdated);
         }
     }
 
@@ -677,8 +681,62 @@
     @Override
     public void onExternalCallChanged(Call call, boolean isExternalCall) {
         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
-        // TODO: Need to add logic which ensures changes to a call's external state adds or removes
-        // the call from the InCallServices depending on whether they support external calls.
+
+        List<ComponentName> componentsUpdated = new ArrayList<>();
+        if (!isExternalCall) {
+            // The call was external but it is no longer external.  We must now add it to any
+            // InCallServices which do not support external calls.
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+                InCallServiceInfo info = entry.getKey();
+
+                if (info.isExternalCallsSupported()) {
+                    // For InCallServices which support external calls, the call will have already
+                    // been added to the connection service, so we do not need to add it again.
+                    continue;
+                }
+
+                componentsUpdated.add(info.getComponentName());
+                IInCallService inCallService = entry.getValue();
+
+                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
+                        info.isExternalCallsSupported());
+                try {
+                    inCallService.addCall(parcelableCall);
+                } catch (RemoteException ignored) {
+                }
+            }
+            Log.i(this, "Previously external call added to components: %s", componentsUpdated);
+        } else {
+            // The call was regular but it is now external.  We must now remove it from any
+            // InCallServices which do not support external calls.
+            // Remove the call by sending a call update indicating the call was disconnected.
+            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                    call,
+                    false /* includeVideoProvider */,
+                    mCallsManager.getPhoneAccountRegistrar(),
+                    false /* supportsExternalCalls */,
+                    android.telecom.Call.STATE_DISCONNECTED /* overrideState */);
+
+            Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+                InCallServiceInfo info = entry.getKey();
+                if (info.isExternalCallsSupported()) {
+                    // For InCallServices which support external calls, we do not need to remove
+                    // the call.
+                    continue;
+                }
+
+                componentsUpdated.add(info.getComponentName());
+                IInCallService inCallService = entry.getValue();
+
+                try {
+                    inCallService.updateCall(parcelableCall);
+                } catch (RemoteException ignored) {
+                }
+            }
+            Log.i(this, "External call removed from components: %s", componentsUpdated);
+        }
     }
 
     @Override
@@ -1032,7 +1090,8 @@
                     inCallService.addCall(ParcelableCallUtils.toParcelableCall(
                             call,
                             true /* includeVideoProvider */,
-                            mCallsManager.getPhoneAccountRegistrar()));
+                            mCallsManager.getPhoneAccountRegistrar(),
+                            info.isExternalCallsSupported()));
                 } catch (RemoteException ignored) {
                 }
             }
@@ -1077,11 +1136,7 @@
      */
     private void updateCall(Call call, boolean videoProviderChanged) {
         if (!mInCallServices.isEmpty()) {
-            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
-                    call,
-                    videoProviderChanged /* includeVideoProvider */,
-                    mCallsManager.getPhoneAccountRegistrar());
-            Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall);
+            Log.i(this, "Sending updateCall %s", call);
             List<ComponentName> componentsUpdated = new ArrayList<>();
             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
                 InCallServiceInfo info = entry.getKey();
@@ -1089,6 +1144,11 @@
                     continue;
                 }
 
+                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                        call,
+                        videoProviderChanged /* includeVideoProvider */,
+                        mCallsManager.getPhoneAccountRegistrar(),
+                        info.isExternalCallsSupported());
                 ComponentName componentName = info.getComponentName();
                 IInCallService inCallService = entry.getValue();
                 componentsUpdated.add(componentName);
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index 874b301..7f12404 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -68,6 +68,7 @@
         public static final String CSW_SET_ACTIVE = "CSW.sA";
         public static final String CSW_SET_RINGING = "CSW.sR";
         public static final String CSW_SET_DIALING = "CSW.sD";
+        public static final String CSW_SET_PULLING = "CSW.sP";
         public static final String CSW_SET_DISCONNECTED = "CSW.sDc";
         public static final String CSW_SET_ON_HOLD = "CSW.sOH";
         public static final String CSW_REMOVE_CALL = "CSW.rC";
@@ -108,6 +109,7 @@
         public static final String DESTROYED = "DESTROYED";
         public static final String SET_CONNECTING = "SET_CONNECTING";
         public static final String SET_DIALING = "SET_DIALING";
+        public static final String SET_PULLING = "SET_PULLING";
         public static final String SET_ACTIVE = "SET_ACTIVE";
         public static final String SET_HOLD = "SET_HOLD";
         public static final String SET_RINGING = "SET_RINGING";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 6f8b238..b64ce9c 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -28,11 +28,13 @@
  * Utilities dealing with {@link ParcelableCall}.
  */
 public class ParcelableCallUtils {
+    private static final int CALL_STATE_OVERRIDE_NONE = -1;
+
     public static class Converter {
         public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
                 PhoneAccountRegistrar phoneAccountRegistrar) {
             return ParcelableCallUtils.toParcelableCall(
-                    call, includeVideoProvider, phoneAccountRegistrar);
+                    call, includeVideoProvider, phoneAccountRegistrar, false);
         }
     }
 
@@ -45,13 +47,45 @@
      *      method creates a {@link VideoCallImpl} instance on access it is important for the
      *      recipient of the {@link ParcelableCall} to know if the video provider changed.
      * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
+     * @param supportsExternalCalls Indicates whether the call should be parcelled for an
+     *      {@link InCallService} which supports external calls or not.
+     */
+    public static ParcelableCall toParcelableCall(
+            Call call,
+            boolean includeVideoProvider,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            boolean supportsExternalCalls) {
+        return toParcelableCall(call, includeVideoProvider, phoneAccountRegistrar,
+                supportsExternalCalls, CALL_STATE_OVERRIDE_NONE /* overrideState */);
+    }
+
+    /**
+     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
+     *
+     * @param call The {@link Call} to parcel.
+     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
+     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
+     *      method creates a {@link VideoCallImpl} instance on access it is important for the
+     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
+     * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
+     * @param supportsExternalCalls Indicates whether the call should be parcelled for an
+     *      {@link InCallService} which supports external calls or not.
+     * @param overrideState When not {@link #CALL_STATE_OVERRIDE_NONE}, use the provided state as an
+     *      override to whatever is defined in the call.
      * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
      */
     public static ParcelableCall toParcelableCall(
             Call call,
             boolean includeVideoProvider,
-            PhoneAccountRegistrar phoneAccountRegistrar) {
-        int state = getParcelableState(call);
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            boolean supportsExternalCalls,
+            int overrideState) {
+        int state;
+        if (overrideState == CALL_STATE_OVERRIDE_NONE) {
+            state = getParcelableState(call, supportsExternalCalls);
+        } else {
+            state = overrideState;
+        }
         int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
         int properties = convertConnectionToCallProperties(call.getConnectionProperties());
         if (call.isConference()) {
@@ -141,7 +175,7 @@
                 call.getExtras());
     }
 
-    private static int getParcelableState(Call call) {
+    private static int getParcelableState(Call call, boolean supportsExternalCalls) {
         int state = CallState.NEW;
         switch (call.getState()) {
             case CallState.ABORTED:
@@ -157,6 +191,19 @@
             case CallState.DIALING:
                 state = android.telecom.Call.STATE_DIALING;
                 break;
+            case CallState.PULLING:
+                if (supportsExternalCalls) {
+                    // The InCallService supports external calls, so it must handle
+                    // STATE_PULLING_CALL.
+                    state = android.telecom.Call.STATE_PULLING_CALL;
+                } else {
+                    // The InCallService does NOT support external calls, so remap
+                    // STATE_PULLING_CALL to STATE_DIALING.  In essence, pulling a call can be seen
+                    // as a form of dialing, so it is appropriate for InCallServices which do not
+                    // handle external calls.
+                    state = android.telecom.Call.STATE_DIALING;
+                }
+                break;
             case CallState.DISCONNECTING:
                 state = android.telecom.Call.STATE_DISCONNECTING;
                 break;
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index b1f7e40..0781ca2 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -86,8 +86,8 @@
         int callState = TelephonyManager.CALL_STATE_IDLE;
         if (mCallsManager.hasRingingCall()) {
             callState = TelephonyManager.CALL_STATE_RINGING;
-        } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.ACTIVE,
-                    CallState.ON_HOLD) != null) {
+        } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.PULLING,
+                CallState.ACTIVE, CallState.ON_HOLD) != null) {
             callState = TelephonyManager.CALL_STATE_OFFHOOK;
         }
         sendPhoneStateChangedBroadcast(call, callState);
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index ecc6e33..7dc0a79 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1294,6 +1294,7 @@
             call = mCallsManager.getFirstCallWithState(
                     CallState.ACTIVE,
                     CallState.DIALING,
+                    CallState.PULLING,
                     CallState.RINGING,
                     CallState.ON_HOLD);
         }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index b30e372..0150dbe 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -165,6 +165,9 @@
             capabilities |= CAPABILITY_RESPOND_VIA_TEXT;
             setConnectionCapabilities(capabilities);
 
+            if (isIncoming) {
+                putExtra(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+            }
             LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
                     mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS));
             final IntentFilter filter =