Merge "Ims: Add support for Adhoc Conference calls" am: ba248894ac am: b54e70f7ae

Change-Id: Idf560b660a3d6d5f026ea240c421d380d4506c2d
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 8773ea2..e158230 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -41,6 +41,7 @@
 import android.telecom.GatewayInfo;
 import android.telecom.Log;
 import android.telecom.Logging.EventManager;
+import android.telecom.ParcelableConference;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -326,6 +327,8 @@
     /** The handle with which to establish this call. */
     private Uri mHandle;
 
+    /** The participants with which to establish adhoc conference call */
+    private List<Uri> mParticipants;
     /**
      * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
      */
@@ -624,15 +627,43 @@
             boolean isConference,
             ClockProxy clockProxy,
             ToastFactory toastFactory) {
+        this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
+               handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
+               targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
+               isConference, clockProxy, toastFactory);
+
+    }
+
+    public Call(
+            String callId,
+            Context context,
+            CallsManager callsManager,
+            TelecomSystem.SyncRoot lock,
+            ConnectionServiceRepository repository,
+            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+            Uri handle,
+            List<Uri> participants,
+            GatewayInfo gatewayInfo,
+            PhoneAccountHandle connectionManagerPhoneAccountHandle,
+            PhoneAccountHandle targetPhoneAccountHandle,
+            int callDirection,
+            boolean shouldAttachToExistingConnection,
+            boolean isConference,
+            ClockProxy clockProxy,
+            ToastFactory toastFactory) {
+
         mId = callId;
         mConnectionId = callId;
-        mState = isConference ? CallState.ACTIVE : CallState.NEW;
+        mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
+                callDirection != CALL_DIRECTION_OUTGOING) ?
+                CallState.ACTIVE : CallState.NEW;
         mContext = context;
         mCallsManager = callsManager;
         mLock = lock;
         mRepository = repository;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
         setHandle(handle);
+        mParticipants = participants;
         mPostDialDigits = handle != null
                 ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
         mGatewayInfo = gatewayInfo;
@@ -655,14 +686,14 @@
      * @param handle The handle to dial.
      * @param gatewayInfo Gateway information to use for the call.
      * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
-*         This account must be one that was registered with the
-*         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
+     * This account must be one that was registered with the
+     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
-*         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
+     * one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
      * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
-*         or CALL_DIRECTION_UNKNOWN
+     * or CALL_DIRECTION_UNKNOWN
      * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
-*         connection, regardless of whether it's incoming or outgoing.
+     * connection, regardless of whether it's incoming or outgoing.
      * @param connectTimeMillis The connection time of the call.
      * @param clockProxy
      */
@@ -1080,6 +1111,16 @@
         return mHandle;
     }
 
+    public List<Uri> getParticipants() {
+        return mParticipants;
+    }
+
+    public boolean isAdhocConferenceCall() {
+        return mIsConference &&
+                (mCallDirection == CALL_DIRECTION_OUTGOING ||
+                mCallDirection == CALL_DIRECTION_INCOMING);
+    }
+
     public String getPostDialDigits() {
         return mPostDialDigits;
     }
@@ -1828,6 +1869,39 @@
     }
 
     @Override
+    public void handleCreateConferenceSuccess(
+            CallIdMapper idMapper,
+            ParcelableConference conference) {
+        Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+        setTargetPhoneAccount(conference.getPhoneAccount());
+        setHandle(conference.getHandle(), conference.getHandlePresentation());
+
+        setConnectionCapabilities(conference.getConnectionCapabilities());
+        setConnectionProperties(conference.getConnectionProperties());
+        setVideoProvider(conference.getVideoProvider());
+        setVideoState(conference.getVideoState());
+        setRingbackRequested(conference.isRingbackRequested());
+        setStatusHints(conference.getStatusHints());
+        putExtras(SOURCE_CONNECTION_SERVICE, conference.getExtras());
+
+        switch (mCallDirection) {
+            case CALL_DIRECTION_INCOMING:
+                // Listeners (just CallsManager for now) will be responsible for checking whether
+                // the call should be blocked.
+                for (Listener l : mListeners) {
+                    l.onSuccessfulIncomingCall(this);
+                }
+                break;
+            case CALL_DIRECTION_OUTGOING:
+                for (Listener l : mListeners) {
+                    l.onSuccessfulOutgoingCall(this,
+                            getStateFromConnectionState(conference.getState()));
+                }
+                break;
+        }
+    }
+
+    @Override
     public void handleCreateConnectionSuccess(
             CallIdMapper idMapper,
             ParcelableConnection connection) {
@@ -1878,6 +1952,26 @@
     }
 
     @Override
+    public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+        clearConnectionService();
+        setDisconnectCause(disconnectCause);
+        mCallsManager.markCallAsDisconnected(this, disconnectCause);
+
+        switch (mCallDirection) {
+            case CALL_DIRECTION_INCOMING:
+                for (Listener listener : mListeners) {
+                    listener.onFailedIncomingCall(this);
+                }
+                break;
+            case CALL_DIRECTION_OUTGOING:
+                for (Listener listener : mListeners) {
+                    listener.onFailedOutgoingCall(this, disconnectCause);
+                }
+                break;
+        }
+    }
+
+    @Override
     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
         clearConnectionService();
         setDisconnectCause(disconnectCause);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 82422db..024a863 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -109,6 +109,7 @@
 import com.android.server.telecom.ui.ToastFactory;
 
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -1153,6 +1154,11 @@
         mListeners.remove(listener);
     }
 
+    void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        Log.d(this, "processIncomingCallConference");
+        processIncomingCallIntent(phoneAccountHandle, extras, true);
+    }
+
     /**
      * Starts the process to attach the call to a connection service.
      *
@@ -1161,6 +1167,11 @@
      * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      */
     void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        processIncomingCallIntent(phoneAccountHandle, extras, false);
+    }
+
+    void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+        boolean isConference) {
         Log.d(this, "processIncomingCallIntent");
         boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
         Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
@@ -1181,7 +1192,7 @@
                 phoneAccountHandle,
                 Call.CALL_DIRECTION_INCOMING /* callDirection */,
                 false /* forceAttachToExistingConnection */,
-                false, /* isConference */
+                isConference, /* isConference */
                 mClockProxy,
                 mToastFactory);
 
@@ -1295,14 +1306,22 @@
 
         if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
                 call.getTargetPhoneAccount()))) {
-            notifyCreateConnectionFailed(phoneAccountHandle, call);
+            if (isConference) {
+                notifyCreateConferenceFailed(phoneAccountHandle, call);
+            } else {
+                notifyCreateConnectionFailed(phoneAccountHandle, call);
+            }
         } else if (isInEmergencyCall()) {
             // The incoming call is implicitly being rejected so the user does not get any incoming
             // call UI during an emergency call. In this case, log the call as missed instead of
             // rejected since the user did not explicitly reject.
             mCallLogManager.logCall(call, Calls.MISSED_TYPE,
                     true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
-            notifyCreateConnectionFailed(phoneAccountHandle, call);
+            if (isConference) {
+                notifyCreateConferenceFailed(phoneAccountHandle, call);
+            } else {
+                notifyCreateConnectionFailed(phoneAccountHandle, call);
+            }
         } else {
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
@@ -1390,7 +1409,18 @@
             PhoneAccountHandle requestedAccountHandle,
             Bundle extras, UserHandle initiatingUser, Intent originalIntent,
             String callingPackage) {
+        final List<Uri> callee = new ArrayList<>();
+        callee.add(handle);
+        return startOutgoingCall(callee, requestedAccountHandle, extras, initiatingUser,
+                originalIntent, callingPackage, false);
+    }
+
+    private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
+            PhoneAccountHandle requestedAccountHandle,
+            Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+            String callingPackage, boolean isConference) {
         boolean isReusedCall;
+        Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
         Call call = reuseOutgoingCall(handle);
 
         PhoneAccount account =
@@ -1406,12 +1436,13 @@
                     mConnectionServiceRepository,
                     mPhoneNumberUtilsAdapter,
                     handle,
+                    isConference ? participants : null,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
                     null /* requestedAccountHandle */,
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
-                    false, /* isConference */
+                    isConference, /* isConference */
                     mClockProxy,
                     mToastFactory);
             call.initAnalytics(callingPackage);
@@ -1475,7 +1506,8 @@
                 CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
                                 findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
                                         VideoProfile.isVideo(finalVideoState),
-                                        finalCall.isEmergencyCall(), initiatingUser),
+                                        finalCall.isEmergencyCall(), initiatingUser,
+                                        isConference),
                         new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP", mLock));
 
         // This is a block of code that executes after the list of potential phone accts has been
@@ -1543,8 +1575,13 @@
                         } else {
                             // If the ongoing call is a managed call, we will prevent the outgoing
                             // call from dialing.
-                            notifyCreateConnectionFailed(
-                                    finalCall.getTargetPhoneAccount(), finalCall);
+                            if (isConference) {
+                                notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(),
+                                    finalCall);
+                            } else {
+                                notifyCreateConnectionFailed(
+                                        finalCall.getTargetPhoneAccount(), finalCall);
+                            }
                         }
                         Log.i(CallsManager.this, "Aborting call since there's no room");
                         return CompletableFuture.completedFuture(null);
@@ -1706,6 +1743,38 @@
         return mLatestPostSelectionProcessingFuture;
     }
 
+    public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
+            UserHandle initiatingUser) {
+
+         if (clientExtras == null) {
+             clientExtras = new Bundle();
+         }
+
+         PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
+                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+         CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
+                 clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
+                 true/* isconference*/);
+
+         final boolean speakerphoneOn = clientExtras.getBoolean(
+                 TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE);
+         final int videoState = clientExtras.getInt(
+                 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE);
+
+         final Session logSubsession = Log.createSubsession();
+         callFuture.thenAccept((call) -> {
+             if (call != null) {
+                 Log.continueSession(logSubsession, "CM.pOGC");
+                 try {
+                     placeOutgoingCall(call, call.getHandle(), null/* gatewayInfo */,
+                             speakerphoneOn, videoState);
+                 } finally {
+                     Log.endSession();
+                 }
+             }
+         });
+    }
+
     /**
      * Performs call identification for an outgoing phone call.
      * @param theCall The outgoing call to perform identification.
@@ -1768,6 +1837,14 @@
     public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
             PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
             boolean isEmergency, UserHandle initiatingUser) {
+       return findOutgoingCallPhoneAccount(targetPhoneAccountHandle, handle, isVideo,
+               isEmergency, initiatingUser, false/* isConference */);
+    }
+
+    public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
+            PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
+            boolean isEmergency, UserHandle initiatingUser, boolean isConference) {
+
         if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
             return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
         }
@@ -1775,12 +1852,13 @@
         List<PhoneAccountHandle> accounts;
         // Try to find a potential phone account, taking into account whether this is a video
         // call.
-        accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo, isEmergency);
+        accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo, isEmergency,
+                isConference);
         if (isVideo && accounts.size() == 0) {
             // Placing a video call but no video capable accounts were found, so consider any
             // call capable accounts (we can fallback to audio).
             accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
-                    false /* isVideo */, isEmergency /* isEmergency */);
+                    false /* isVideo */, isEmergency /* isEmergency */, isConference);
         }
         Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
 
@@ -2057,7 +2135,11 @@
             // If the account has been set, proceed to place the outgoing call.
             // Otherwise the connection will be initiated when the account is set by the user.
             if (call.isSelfManaged() && !isOutgoingCallPermitted) {
-                notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+                if (call.isAdhocConferenceCall()) {
+                    notifyCreateConferenceFailed(call.getTargetPhoneAccount(), call);
+                } else {
+                    notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+                }
             } else {
                 if (call.isEmergencyCall()) {
                     // Drop any ongoing self-managed calls to make way for an emergency call.
@@ -2458,15 +2540,23 @@
     @VisibleForTesting
     public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
             boolean isVideo, boolean isEmergency) {
+        return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
+    }
+
+    public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
+            boolean isVideo, boolean isEmergency, boolean isConference) {
+
         if (handle == null) {
             return Collections.emptyList();
         }
         // If we're specifically looking for video capable accounts, then include that capability,
         // otherwise specify no additional capability constraints. When handling the emergency call,
         // it also needs to find the phone accounts excluded by CAPABILITY_EMERGENCY_CALLS_ONLY.
+        int capabilities = isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0;
+        capabilities |= isConference ? PhoneAccount.CAPABILITY_ADHOC_CONFERENCE_CALLING : 0;
         List<PhoneAccountHandle> allAccounts =
                 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
-                        isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0 /* any */,
+                        capabilities,
                         isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
         if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
             mMaxNumberOfSimultaneouslyActiveSims =
@@ -4364,6 +4454,29 @@
 
     /**
      * Notifies the {@link android.telecom.ConnectionService} associated with a
+     * {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
+     *
+     * @param phoneAccountHandle The {@link PhoneAccountHandle}.
+     * @param call The {@link Call} which could not be added.
+     */
+    private void notifyCreateConferenceFailed(PhoneAccountHandle phoneAccountHandle, Call call) {
+        if (phoneAccountHandle == null) {
+            return;
+        }
+        ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
+                phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+        if (service == null) {
+            Log.i(this, "Found no connection service.");
+            return;
+        } else {
+            call.setConnectionService(service);
+            service.createConferenceFailed(call);
+        }
+    }
+
+
+    /**
+     * Notifies the {@link android.telecom.ConnectionService} associated with a
      * {@link PhoneAccountHandle} that the attempt to handover a call has failed.
      *
      * @param call The handover call
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index dd50318..ca513da 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -99,6 +99,36 @@
         }
 
         @Override
+        public void handleCreateConferenceComplete(String callId, ConnectionRequest request,
+                ParcelableConference conference, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("handleCreateConferenceComplete %s", callId);
+                    ConnectionServiceWrapper.this
+                            .handleCreateConferenceComplete(callId, request, conference);
+
+                    if (mServiceInterface != null) {
+                        logOutgoing("createConferenceComplete %s", callId);
+                        try {
+                            mServiceInterface.createConferenceComplete(callId,
+                                    Log.getExternalSession());
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+
+        @Override
         public void setActive(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ACTIVE);
             long token = Binder.clearCallingIdentity();
@@ -1093,6 +1123,66 @@
     }
 
     /**
+     * Creates a conference for a new outgoing call or attach to an existing incoming call.
+     */
+    public void createConference(final Call call, final CreateConnectionResponse response) {
+        Log.d(this, "createConference(%s) via %s.", call, getComponentName());
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                String callId = mCallIdMapper.getCallId(call);
+                mPendingResponses.put(callId, response);
+
+                Bundle extras = call.getIntentExtras();
+
+                Log.addEvent(call, LogUtils.Events.START_CONFERENCE,
+                        Log.piiHandle(call.getHandle()));
+
+                ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+                        .setAccountHandle(call.getTargetPhoneAccount())
+                        .setAddress(call.getHandle())
+                        .setExtras(extras)
+                        .setVideoState(call.getVideoState())
+                        .setTelecomCallId(callId)
+                        // For self-managed incoming calls, if there is another ongoing call Telecom
+                        // is responsible for showing a UI to ask the user if they'd like to answer
+                        // this new incoming call.
+                        .setShouldShowIncomingCallUi(
+                                !mCallsManager.shouldShowSystemIncomingCallUi(call))
+                        .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
+                        .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
+                        .setParticipants(call.getParticipants())
+                        .setIsAdhocConferenceCall(call.isAdhocConferenceCall())
+                        .build();
+
+                try {
+                    mServiceInterface.createConference(
+                            call.getConnectionManagerPhoneAccount(),
+                            callId,
+                            connectionRequest,
+                            call.shouldAttachToExistingConnection(),
+                            call.isUnknown(),
+                            Log.getExternalSession());
+
+                } catch (RemoteException e) {
+                    Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+                    mPendingResponses.remove(callId).handleCreateConferenceFailure(
+                            new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+                }
+            }
+
+            @Override
+            public void onFailure() {
+                Log.e(this, new Exception(), "Failure to conference %s", getComponentName());
+                response.handleCreateConferenceFailure(new DisconnectCause(DisconnectCause.ERROR));
+            }
+        };
+
+        mBinder.bind(callback, call);
+
+    }
+
+    /**
      * Creates a new connection for a new outgoing call or to attach to an existing incoming call.
      */
     @VisibleForTesting
@@ -1225,6 +1315,53 @@
         mBinder.bind(callback, call);
     }
 
+    /**
+     * Notifies the {@link ConnectionService} associated with a {@link Call} that the request to
+     * create a conference has been denied or failed.
+     * @param call The call.
+     */
+    void createConferenceFailed(final Call call) {
+        Log.d(this, "createConferenceFailed(%s) via %s.", call, getComponentName());
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                final String callId = mCallIdMapper.getCallId(call);
+                // If still bound, tell the connection service create connection has failed.
+                if (callId != null && isServiceValid("createConferenceFailed")) {
+                    Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_FAILED,
+                            Log.piiHandle(call.getHandle()));
+                    try {
+                        logOutgoing("createConferenceFailed %s", callId);
+                        mServiceInterface.createConferenceFailed(
+                                call.getConnectionManagerPhoneAccount(),
+                                callId,
+                                new ConnectionRequest(
+                                        call.getTargetPhoneAccount(),
+                                        call.getHandle(),
+                                        call.getIntentExtras(),
+                                        call.getVideoState(),
+                                        callId,
+                                        false),
+                                call.isIncoming(),
+                                Log.getExternalSession());
+                        call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
+                        call.disconnect();
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+
+            @Override
+            public void onFailure() {
+                // Binding failed.  Oh no.
+                Log.w(this, "onFailure - could not bind to CS for conf call %s", call.getId());
+            }
+        };
+
+        mBinder.bind(callback, call);
+    }
+
+
     void handoverFailed(final Call call, final int reason) {
         Log.d(this, "handoverFailed(%s) via %s.", call, getComponentName());
         BindCallback callback = new BindCallback() {
@@ -1696,6 +1833,26 @@
         }
     }
 
+    private void handleCreateConferenceComplete(
+            String callId,
+            ConnectionRequest request,
+            ParcelableConference conference) {
+        // TODO: Note we are not using parameter "request", which is a side effect of our tacit
+        // assumption that we have at most one outgoing conference attempt per ConnectionService.
+        // This may not continue to be the case.
+        if (conference.getState() == Connection.STATE_DISCONNECTED) {
+            // A conference that begins in the DISCONNECTED state is an indication of
+            // failure to connect; we handle all failures uniformly
+            removeCall(callId, conference.getDisconnectCause());
+        } else {
+            // Successful connection
+            if (mPendingResponses.containsKey(callId)) {
+                mPendingResponses.remove(callId)
+                        .handleCreateConferenceSuccess(mCallIdMapper, conference);
+            }
+        }
+    }
+
     /**
      * Called when the associated connection service dies.
      */
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 72c06a3..bfd625f 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
+import android.telecom.ParcelableConference;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -245,7 +246,11 @@
                 mCall.setConnectionService(mService);
                 setTimeoutIfNeeded(mService, attempt);
                 if (mCall.isIncoming()) {
-                    mService.createConnection(mCall, CreateConnectionProcessor.this);
+                    if (mCall.isAdhocConferenceCall()) {
+                        mService.createConference(mCall, CreateConnectionProcessor.this);
+                    } else {
+                        mService.createConnection(mCall, CreateConnectionProcessor.this);
+                    }
                 } else {
                     // Start to create the connection for outgoing call after the ConnectionService
                     // of the call has gained the focus.
@@ -254,10 +259,16 @@
                             new CallsManager.RequestCallback(new CallsManager.PendingAction() {
                                 @Override
                                 public void performAction() {
-                                    Log.d(this, "perform create connection");
-                                    mService.createConnection(
-                                            mCall,
-                                            CreateConnectionProcessor.this);
+                                    if (mCall.isAdhocConferenceCall()) {
+                                        Log.d(this, "perform create conference");
+                                        mService.createConference(mCall,
+                                                CreateConnectionProcessor.this);
+                                    } else {
+                                        Log.d(this, "perform create connection");
+                                        mService.createConnection(
+                                                mCall,
+                                                CreateConnectionProcessor.this);
+                                    }
                                 }
                             }));
 
@@ -267,7 +278,11 @@
             Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
             DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
                     mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
-            notifyCallConnectionFailure(disconnectCause);
+            if (mCall.isAdhocConferenceCall()) {
+                notifyConferenceCallFailure(disconnectCause);
+            } else {
+                notifyCallConnectionFailure(disconnectCause);
+            }
         }
     }
 
@@ -457,6 +472,16 @@
         }
     }
 
+    private void notifyConferenceCallFailure(DisconnectCause errorDisconnectCause) {
+        if (mCallResponse != null) {
+            clearTimeout();
+            mCallResponse.handleCreateConferenceFailure(errorDisconnectCause);
+            mCallResponse = null;
+            mCall.clearConnectionService();
+        }
+    }
+
+
     @Override
     public void handleCreateConnectionSuccess(
             CallIdMapper idMapper,
@@ -475,6 +500,25 @@
         }
     }
 
+    @Override
+    public void handleCreateConferenceSuccess(
+            CallIdMapper idMapper,
+            ParcelableConference conference) {
+        if (mCallResponse == null) {
+            // Nobody is listening for this conference attempt any longer; ask the responsible
+            // ConnectionService to tear down any resources associated with the call
+            mService.abort(mCall);
+        } else {
+            // Success -- share the good news and remember that we are no longer interested
+            // in hearing about any more attempts
+            mCallResponse.handleCreateConferenceSuccess(idMapper, conference);
+            mCallResponse = null;
+            // If there's a timeout running then don't clear it. The timeout can be triggered
+            // after the call has successfully been created but before it has become active.
+        }
+    }
+
+
     private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) {
         // Connection Manager does not exist or does not match registered Connection Manager
         // Since Connection manager is a proxy for SIM, fall back to SIM
@@ -522,6 +566,18 @@
         attemptNextPhoneAccount();
     }
 
+    @Override
+    public void handleCreateConferenceFailure(DisconnectCause errorDisconnectCause) {
+        // Failure of some sort; record the reasons for failure and try again if possible
+        Log.d(CreateConnectionProcessor.this, "Conference failed: (%s)", errorDisconnectCause);
+        if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) {
+            notifyConferenceCallFailure(errorDisconnectCause);
+            return;
+        }
+        mLastErrorDisconnectCause = errorDisconnectCause;
+        attemptNextPhoneAccount();
+    }
+
     public void sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts,
             PhoneAccount userPreferredAccount) {
         // Sort the accounts according to how we want to display them (ascending order).
diff --git a/src/com/android/server/telecom/CreateConnectionResponse.java b/src/com/android/server/telecom/CreateConnectionResponse.java
index 8e3d0cf..601a165 100644
--- a/src/com/android/server/telecom/CreateConnectionResponse.java
+++ b/src/com/android/server/telecom/CreateConnectionResponse.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.telecom.DisconnectCause;
+import android.telecom.ParcelableConference;
 import android.telecom.ParcelableConnection;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -28,4 +29,7 @@
 public interface CreateConnectionResponse {
     void handleCreateConnectionSuccess(CallIdMapper idMapper, ParcelableConnection connection);
     void handleCreateConnectionFailure(DisconnectCause disconnectCaused);
+
+    void handleCreateConferenceSuccess(CallIdMapper idMapper, ParcelableConference conference);
+    void handleCreateConferenceFailure(DisconnectCause disconnectCaused);
 }
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 9b089a2..f586a9d 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -120,6 +120,8 @@
         public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
         public static final String START_CONNECTION = "START_CONNECTION";
         public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
+        public static final String START_CONFERENCE = "START_CONFERENCE";
+        public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
         public static final String BIND_CS = "BIND_CS";
         public static final String CS_BOUND = "CS_BOUND";
         public static final String CONFERENCE_WITH = "CONF_WITH";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 69d9c5e..d1d8f0d 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -558,7 +558,10 @@
         android.telecom.Call.Details.PROPERTY_RTT,
 
         Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
-        android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL
+        android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
+
+        Connection.PROPERTY_IS_ADHOC_CONFERENCE,
+        android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE
     };
 
     private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 3daa452..b75c0a6 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1172,6 +1172,54 @@
         }
 
         /**
+         * @see android.telecom.TelecomManager#addNewIncomingConference
+         */
+        @Override
+        public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+            try {
+                Log.startSession("TSI.aNIC");
+                synchronized (mLock) {
+                    Log.i(this, "Adding new incoming conference with phoneAccountHandle %s",
+                            phoneAccountHandle);
+                    if (phoneAccountHandle != null &&
+                            phoneAccountHandle.getComponentName() != null) {
+                        if (isCallerSimCallManager(phoneAccountHandle)
+                                && TelephonyUtil.isPstnComponentName(
+                                        phoneAccountHandle.getComponentName())) {
+                            Log.v(this, "Allowing call manager to add incoming conference" +
+                                    " with PSTN handle");
+                        } else {
+                            mAppOpsManager.checkPackage(
+                                    Binder.getCallingUid(),
+                                    phoneAccountHandle.getComponentName().getPackageName());
+                            // Make sure it doesn't cross the UserHandle boundary
+                            enforceUserHandleMatchesCaller(phoneAccountHandle);
+                            enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
+                                    Binder.getCallingUserHandle());
+                            if (isSelfManagedConnectionService(phoneAccountHandle)) {
+                                throw new SecurityException("Self-Managed ConnectionServices cannot add "
+                                        + "adhoc conference calls");
+                            }
+                        }
+                        long token = Binder.clearCallingIdentity();
+                        try {
+                            mCallsManager.processIncomingConference(
+                                    phoneAccountHandle, extras);
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    } else {
+                        Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
+                                " incoming conference");
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+
+        /**
          * @see android.telecom.TelecomManager#acceptHandover
          */
         @Override
@@ -1274,6 +1322,25 @@
         }
 
         /**
+         * @see android.telecom.TelecomManager#startConference.
+         */
+        @Override
+        public void startConference(List<Uri> participants, Bundle extras,
+                String callingPackage) {
+            try {
+                Log.startSession("TSI.sC");
+                if (!canCallPhone(callingPackage, "startConference")) {
+                    throw new SecurityException("Package " + callingPackage + " is not allowed"
+                            + " to start conference call");
+                }
+                mCallsManager.startConference(participants, extras, callingPackage,
+                        Binder.getCallingUserHandle());
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * @see android.telecom.TelecomManager#placeCall
          */
         @Override
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 131e591..bcac5f1 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -268,6 +268,26 @@
         }
 
         @Override
+        public void createConference(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String id,
+                ConnectionRequest request,
+                boolean isIncoming,
+                boolean isUnknown,
+                Session.Info sessionInfo) { }
+
+        @Override
+        public void createConferenceComplete(String id, Session.Info sessionInfo) { }
+
+        @Override
+        public void createConferenceFailed(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String callId,
+                ConnectionRequest request,
+                boolean isIncoming,
+                Session.Info sessionInfo) { }
+
+        @Override
         public void abort(String callId, Session.Info info) throws RemoteException { }
 
         @Override