Ims: Add support for Adhoc Conference calls

Add support for Adhoc Conference calls

Test: Manual
Bug: 62151032
Change-Id: Id50d235595d2133f867848ffdebdfe11e2f1c896
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 86ad795..0ebf71f 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -548,8 +548,14 @@
          */
         public static final int PROPERTY_VOIP_AUDIO_MODE = 0x00001000;
 
+        /**
+         * Indicates that the call is an adhoc conference call. This property can be set for both
+         * incoming and outgoing calls.
+         */
+        public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000;
+
         //******************************************************************************************
-        // Next PROPERTY value: 0x00002000
+        // Next PROPERTY value: 0x00004000
         //******************************************************************************************
 
         private final String mTelecomCallId;
@@ -726,6 +732,9 @@
             if (hasProperty(properties, PROPERTY_VOIP_AUDIO_MODE)) {
                 builder.append(" PROPERTY_VOIP_AUDIO_MODE");
             }
+            if (hasProperty(properties, PROPERTY_IS_ADHOC_CONFERENCE)) {
+                builder.append(" PROPERTY_IS_ADHOC_CONFERENCE");
+            }
             builder.append("]");
             return builder.toString();
         }
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 456290cd7..6b0845f 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -69,6 +69,7 @@
         public void onConnectionEvent(Conference c, String event, Bundle extras) {}
         public void onCallerDisplayNameChanged(
                 Conference c, String callerDisplayName, int presentation) {}
+        public void onRingbackRequested(Conference c, boolean ringback) {}
     }
 
     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -97,6 +98,7 @@
     private int mAddressPresentation;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
+    private boolean mRingbackRequested = false;
 
     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
         @Override
@@ -170,6 +172,14 @@
     }
 
     /**
+     * Returns whether this conference is requesting that the system play a ringback tone
+     * on its behalf.
+     */
+    public final boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
+
+    /**
      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
      * {@link Connection} for valid values.
      *
@@ -308,6 +318,35 @@
     public void onConnectionAdded(Connection connection) {}
 
     /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to accept.
+     * For managed {@link ConnectionService}s, this will be called when the user answers a call via
+     * the default dialer's {@link InCallService}.
+     *
+     * @param videoState The video state in which to answer the connection.
+     */
+    public void onAnswer(int videoState) {}
+
+    /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to accept.
+     * For managed {@link ConnectionService}s, this will be called when the user answers a call via
+     * the default dialer's {@link InCallService}.
+     * @hide
+     */
+    public final void onAnswer() {
+         onAnswer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to reject.
+     * For managed {@link ConnectionService}s, this will be called when the user rejects a call via
+     * the default dialer's {@link InCallService}.
+     */
+    public void onReject() {}
+
+    /**
      * Sets state to be on hold.
      */
     public final void setOnHold() {
@@ -322,9 +361,17 @@
     }
 
     /**
+     * Sets state to be ringing.
+     */
+    public final void setRinging() {
+        setState(Connection.STATE_RINGING);
+    }
+
+    /**
      * Sets state to be active.
      */
     public final void setActive() {
+        setRingbackRequested(false);
         setState(Connection.STATE_ACTIVE);
     }
 
@@ -436,6 +483,21 @@
     }
 
     /**
+     * Requests that the framework play a ringback tone. This is to be invoked by implementations
+     * that do not play a ringback tone themselves in the conference's audio stream.
+     *
+     * @param ringback Whether the ringback tone is to be played.
+     */
+    public final void setRingbackRequested(boolean ringback) {
+        if (mRingbackRequested != ringback) {
+            mRingbackRequested = ringback;
+            for (Listener l : mListeners) {
+                l.onRingbackRequested(this, ringback);
+            }
+        }
+    }
+
+    /**
      * Set the video state for the conference.
      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
      * {@link VideoProfile#STATE_BIDIRECTIONAL},
@@ -640,14 +702,6 @@
     }
 
     private void setState(int newState) {
-        if (newState != Connection.STATE_ACTIVE &&
-                newState != Connection.STATE_HOLDING &&
-                newState != Connection.STATE_DISCONNECTED) {
-            Log.w(this, "Unsupported state transition for Conference call.",
-                    Connection.stateToString(newState));
-            return;
-        }
-
         if (mState != newState) {
             int oldState = mState;
             mState = newState;
@@ -657,6 +711,37 @@
         }
     }
 
+    private static class FailureSignalingConference extends Conference {
+        private boolean mImmutable = false;
+        public FailureSignalingConference(DisconnectCause disconnectCause,
+                PhoneAccountHandle phoneAccount) {
+            super(phoneAccount);
+            setDisconnected(disconnectCause);
+            mImmutable = true;
+        }
+        public void checkImmutable() {
+            if (mImmutable) {
+                throw new UnsupportedOperationException("Conference is immutable");
+            }
+        }
+    }
+
+    /**
+     * Return a {@code Conference} which represents a failed conference attempt. The returned
+     * {@code Conference} will have a {@link android.telecom.DisconnectCause} and as specified,
+     * and a {@link #getState()} of {@code STATE_DISCONNECTED}.
+     * <p>
+     * The returned {@code Conference} can be assumed to {@link #destroy()} itself when appropriate,
+     * so users of this method need not maintain a reference to its return value to destroy it.
+     *
+     * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}).
+     * @return A {@code Conference} which indicates failure.
+     */
+    public @NonNull static Conference createFailedConference(
+            @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) {
+        return new FailureSignalingConference(disconnectCause, phoneAccount);
+    }
+
     private final void clearConferenceableList() {
         for (Connection c : mConferenceableConnections) {
             c.removeConnectionListener(mConnectionDeathListener);
@@ -667,11 +752,13 @@
     @Override
     public String toString() {
         return String.format(Locale.US,
-                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
+                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s,"
+                + "isRingbackRequested: %s, ThisObject %s]",
                 Connection.stateToString(mState),
                 Call.Details.capabilitiesToString(mConnectionCapabilities),
                 getVideoState(),
                 getVideoProvider(),
+                isRingbackRequested() ? "Y" : "N",
                 super.toString());
     }
 
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 0becaf2..a6f79fc 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -497,8 +497,17 @@
     @TestApi
     public static final int PROPERTY_REMOTELY_HOSTED = 1 << 11;
 
+    /**
+     * Set by the framework to indicate that it is an adhoc conference call.
+     * <p>
+     * This is used for Outgoing and incoming conference calls.
+     *
+     */
+    public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12;
+
+
     //**********************************************************************************************
-    // Next PROPERTY value: 1<<12
+    // Next PROPERTY value: 1<<13
     //**********************************************************************************************
 
     /**
@@ -953,6 +962,10 @@
             builder.append(isLong ? " PROPERTY_REMOTELY_HOSTED" : " remote_hst");
         }
 
+        if ((properties & PROPERTY_IS_ADHOC_CONFERENCE) == PROPERTY_IS_ADHOC_CONFERENCE) {
+            builder.append(isLong ? " PROPERTY_IS_ADHOC_CONFERENCE" : " adhoc_conf");
+        }
+
         builder.append("]");
         return builder.toString();
     }
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 221f8f1..6d7ceca 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -26,6 +26,9 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Simple data container encapsulating a request to some entity to
  * create a new {@link Connection}.
@@ -46,6 +49,8 @@
         private boolean mShouldShowIncomingCallUi = false;
         private ParcelFileDescriptor mRttPipeToInCall;
         private ParcelFileDescriptor mRttPipeFromInCall;
+        private List<Uri> mParticipants;
+        private boolean mIsAdhocConference = false;
 
         public Builder() { }
 
@@ -59,6 +64,15 @@
         }
 
         /**
+         * Sets the participants for the resulting {@link ConnectionRequest}
+         * @param participants The participants to which the {@link Connection} is to connect.
+         */
+        public @NonNull Builder setParticipants(@Nullable List<Uri> participants) {
+            this.mParticipants = participants;
+            return this;
+        }
+
+        /**
          * Sets the address for the resulting {@link ConnectionRequest}
          * @param address The address(e.g., phone number) to which the {@link Connection} is to
          *                connect.
@@ -108,6 +122,16 @@
         }
 
         /**
+         * Sets isAdhocConference for the resulting {@link ConnectionRequest}
+         * @param isAdhocConference {@code true} if it is a adhoc conference call
+         *                          {@code false}, if not a adhoc conference call
+         */
+        public @NonNull Builder setIsAdhocConferenceCall(boolean isAdhocConference) {
+            this.mIsAdhocConference = isAdhocConference;
+            return this;
+        }
+
+        /**
          * Sets the RTT pipe for transferring text into the {@link ConnectionService} for the
          * resulting {@link ConnectionRequest}
          * @param rttPipeFromInCall The data pipe to read from.
@@ -141,7 +165,9 @@
                     mTelecomCallId,
                     mShouldShowIncomingCallUi,
                     mRttPipeFromInCall,
-                    mRttPipeToInCall);
+                    mRttPipeToInCall,
+                    mParticipants,
+                    mIsAdhocConference);
         }
     }
 
@@ -155,6 +181,8 @@
     private final ParcelFileDescriptor mRttPipeFromInCall;
     // Cached return value of getRttTextStream -- we don't want to wrap it more than once.
     private Connection.RttTextStream mRttTextStream;
+    private List<Uri> mParticipants;
+    private final boolean mIsAdhocConference;
 
     /**
      * @param accountHandle The accountHandle which should be used to place the call.
@@ -214,6 +242,21 @@
             boolean shouldShowIncomingCallUi,
             ParcelFileDescriptor rttPipeFromInCall,
             ParcelFileDescriptor rttPipeToInCall) {
+        this(accountHandle, handle, extras, videoState, telecomCallId,
+                shouldShowIncomingCallUi, rttPipeFromInCall, rttPipeToInCall, null, false);
+    }
+
+    private ConnectionRequest(
+            PhoneAccountHandle accountHandle,
+            Uri handle,
+            Bundle extras,
+            int videoState,
+            String telecomCallId,
+            boolean shouldShowIncomingCallUi,
+            ParcelFileDescriptor rttPipeFromInCall,
+            ParcelFileDescriptor rttPipeToInCall,
+            List<Uri> participants,
+            boolean isAdhocConference) {
         mAccountHandle = accountHandle;
         mAddress = handle;
         mExtras = extras;
@@ -222,6 +265,8 @@
         mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
         mRttPipeFromInCall = rttPipeFromInCall;
         mRttPipeToInCall = rttPipeToInCall;
+        mParticipants = participants;
+        mIsAdhocConference = isAdhocConference;
     }
 
     private ConnectionRequest(Parcel in) {
@@ -233,6 +278,11 @@
         mShouldShowIncomingCallUi = in.readInt() == 1;
         mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
         mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
+
+        mParticipants = new ArrayList<Uri>();
+        in.readList(mParticipants, getClass().getClassLoader());
+
+        mIsAdhocConference = in.readInt() == 1;
     }
 
     /**
@@ -246,6 +296,11 @@
     public Uri getAddress() { return mAddress; }
 
     /**
+     * The participants to which the {@link Connection} is to connect.
+     */
+    public @Nullable List<Uri> getParticipants() { return mParticipants; }
+
+    /**
      * Application-specific extra data. Used for passing back information from an incoming
      * call {@code Intent}, and for any proprietary extensions arranged between a client
      * and servant {@code ConnectionService} which agree on a vocabulary for such data.
@@ -290,6 +345,13 @@
     }
 
     /**
+     * @return {@code true} if the call is a adhoc conference call else @return {@code false}
+     */
+    public boolean isAdhocConferenceCall() {
+        return mIsAdhocConference;
+    }
+
+    /**
      * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the connection
      * service to the in-call UI. In order to obtain an
      * {@link java.io.InputStream} from this {@link ParcelFileDescriptor}, use
@@ -345,11 +407,12 @@
 
     @Override
     public String toString() {
-        return String.format("ConnectionRequest %s %s",
+        return String.format("ConnectionRequest %s %s isAdhocConf: %s",
                 mAddress == null
                         ? Uri.EMPTY
                         : Connection.toLogSafePhoneNumber(mAddress.toString()),
-                bundleToString(mExtras));
+                bundleToString(mExtras),
+                isAdhocConferenceCall() ? "Y" : "N");
     }
 
     private static String bundleToString(Bundle extras){
@@ -406,5 +469,7 @@
         destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0);
         destination.writeParcelable(mRttPipeFromInCall, 0);
         destination.writeParcelable(mRttPipeToInCall, 0);
+        destination.writeList(mParticipants);
+        destination.writeInt(mIsAdhocConference ? 1 : 0);
     }
 }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3a0494e..440f044 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -154,6 +154,9 @@
     private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL";
     private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG";
     private static final String SESSION_HANDOVER_FAILED = "CS.haF";
+    private static final String SESSION_CREATE_CONF = "CS.crConf";
+    private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC";
+    private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF";
 
     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
     private static final int MSG_CREATE_CONNECTION = 2;
@@ -188,6 +191,9 @@
     private static final int MSG_HANDOVER_FAILED = 32;
     private static final int MSG_HANDOVER_COMPLETE = 33;
     private static final int MSG_DEFLECT = 34;
+    private static final int MSG_CREATE_CONFERENCE = 35;
+    private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
+    private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
 
     private static Connection sNullConnection;
 
@@ -291,6 +297,63 @@
         }
 
         @Override
+        public void createConference(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String id,
+                ConnectionRequest request,
+                boolean isIncoming,
+                boolean isUnknown,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = connectionManagerPhoneAccount;
+                args.arg2 = id;
+                args.arg3 = request;
+                args.arg4 = Log.createSubsession();
+                args.argi1 = isIncoming ? 1 : 0;
+                args.argi2 = isUnknown ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void createConferenceComplete(String id, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF_COMPLETE);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = id;
+                args.arg2 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE_COMPLETE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void createConferenceFailed(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String callId,
+                ConnectionRequest request,
+                boolean isIncoming,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF_FAILED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = request;
+                args.arg3 = Log.createSubsession();
+                args.arg4 = connectionManagerPhoneAccount;
+                args.argi1 = isIncoming ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE_FAILED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void handoverFailed(String callId, ConnectionRequest request, int reason,
                                    Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED);
@@ -802,6 +865,106 @@
                     }
                     break;
                 }
+                case MSG_CREATE_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
+                    try {
+                        final PhoneAccountHandle connectionManagerPhoneAccount =
+                                (PhoneAccountHandle) args.arg1;
+                        final String id = (String) args.arg2;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
+                        final boolean isIncoming = args.argi1 == 1;
+                        final boolean isUnknown = args.argi2 == 1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-initconference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF + ".pIConfR",
+                                            null /*lock*/) {
+                                @Override
+                                public void loggedRun() {
+                                    createConference(connectionManagerPhoneAccount,
+                                            id,
+                                            request,
+                                            isIncoming,
+                                            isUnknown);
+                                }
+                            }.prepare());
+                        } else {
+                            createConference(connectionManagerPhoneAccount,
+                                    id,
+                                    request,
+                                    isIncoming,
+                                    isUnknown);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_CREATE_CONFERENCE_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg2,
+                            SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE);
+                    try {
+                        final String id = (String) args.arg1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init conference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF_COMPLETE
+                                                    + ".pIConfR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            notifyCreateConferenceComplete(id);
+                                        }
+                                    }.prepare());
+                        } else {
+                            notifyCreateConferenceComplete(id);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_CREATE_CONFERENCE_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER +
+                            SESSION_CREATE_CONN_FAILED);
+                    try {
+                        final String id = (String) args.arg1;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg2;
+                        final boolean isIncoming = args.argi1 == 1;
+                        final PhoneAccountHandle connectionMgrPhoneAccount =
+                                (PhoneAccountHandle) args.arg4;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init conference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF_FAILED
+                                                    + ".pIConfR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            createConferenceFailed(connectionMgrPhoneAccount, id,
+                                                    request, isIncoming);
+                                        }
+                                    }.prepare());
+                        } else {
+                            Log.i(this, "createConferenceFailed %s", id);
+                            createConferenceFailed(connectionMgrPhoneAccount, id, request,
+                                    isIncoming);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+
                 case MSG_HANDOVER_FAILED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg3, SESSION_HANDLER +
@@ -1162,6 +1325,12 @@
         public void onStateChanged(Conference conference, int oldState, int newState) {
             String id = mIdByConference.get(conference);
             switch (newState) {
+                case Connection.STATE_RINGING:
+                    mAdapter.setRinging(id);
+                    break;
+                case Connection.STATE_DIALING:
+                    mAdapter.setDialing(id);
+                    break;
                 case Connection.STATE_ACTIVE:
                     mAdapter.setActive(id);
                     break;
@@ -1292,6 +1461,13 @@
                 mAdapter.onConnectionEvent(id, event, extras);
             }
         }
+
+        @Override
+        public void onRingbackRequested(Conference c, boolean ringback) {
+            String id = mIdByConference.get(c);
+            Log.d(this, "Adapter conference onRingback %b", ringback);
+            mAdapter.setRingbackRequested(id, ringback);
+        }
     };
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
@@ -1534,6 +1710,70 @@
         return super.onUnbind(intent);
     }
 
+
+    /**
+     * This can be used by telecom to either create a new outgoing conference call or attach
+     * to an existing incoming conference call. In either case, telecom will cycle through a
+     * set of services and call createConference until a connection service cancels the process
+     * or completes it successfully.
+     */
+    private void createConference(
+            final PhoneAccountHandle callManagerAccount,
+            final String callId,
+            final ConnectionRequest request,
+            boolean isIncoming,
+            boolean isUnknown) {
+
+        Conference conference = null;
+        conference = isIncoming ? onCreateIncomingConference(callManagerAccount, request)
+                    : onCreateOutgoingConference(callManagerAccount, request);
+
+        Log.d(this, "createConference, conference: %s", conference);
+        if (conference == null) {
+            Log.i(this, "createConference, implementation returned null conference.");
+            conference = Conference.createFailedConference(
+                    new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"),
+                    request.getAccountHandle());
+        }
+        if (conference.getExtras() != null) {
+            conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+        }
+        mConferenceById.put(callId, conference);
+        mIdByConference.put(conference, callId);
+        conference.addListener(mConferenceListener);
+        ParcelableConference parcelableConference = new ParcelableConference(
+                request.getAccountHandle(),
+                conference.getState(),
+                conference.getConnectionCapabilities(),
+                conference.getConnectionProperties(),
+                Collections.<String>emptyList(), //connectionIds
+                conference.getVideoProvider() == null ?
+                        null : conference.getVideoProvider().getInterface(),
+                conference.getVideoState(),
+                conference.getConnectTimeMillis(),
+                conference.getConnectionStartElapsedRealTime(),
+                conference.getStatusHints(),
+                conference.getExtras(),
+                conference.getAddress(),
+                conference.getAddressPresentation(),
+                conference.getCallerDisplayName(),
+                conference.getCallerDisplayNamePresentation(),
+                conference.getDisconnectCause(),
+                conference.isRingbackRequested());
+        if (conference.getState() != Connection.STATE_DISCONNECTED) {
+            conference.setTelecomCallId(callId);
+            mAdapter.setVideoProvider(callId, conference.getVideoProvider());
+            mAdapter.setVideoState(callId, conference.getVideoState());
+            onConferenceAdded(conference);
+        }
+
+        Log.d(this, "createConference, calling handleCreateConferenceSuccessful %s", callId);
+        mAdapter.handleCreateConferenceComplete(
+                callId,
+                request,
+                parcelableConference);
+    }
+
     /**
      * This can be used by telecom to either create a new outgoing call or attach to an existing
      * incoming call. In either case, telecom will cycle through a set of services and call
@@ -1645,6 +1885,18 @@
         }
     }
 
+    private void createConferenceFailed(final PhoneAccountHandle callManagerAccount,
+                                        final String callId, final ConnectionRequest request,
+                                        boolean isIncoming) {
+
+        Log.i(this, "createConferenceFailed %s", callId);
+        if (isIncoming) {
+            onCreateIncomingConferenceFailed(callManagerAccount, request);
+        } else {
+            onCreateOutgoingConferenceFailed(callManagerAccount, request);
+        }
+    }
+
     private void handoverFailed(final String callId, final ConnectionRequest request,
                                         int reason) {
 
@@ -1669,6 +1921,24 @@
                 "notifyCreateConnectionComplete"));
     }
 
+    /**
+     * Called by Telecom when the creation of a new Conference has completed and it is now added
+     * to Telecom.
+     * @param callId The ID of the connection.
+     */
+    private void notifyCreateConferenceComplete(final String callId) {
+        Log.i(this, "notifyCreateConferenceComplete %s", callId);
+        if (callId == null) {
+            // This could happen if the conference fails quickly and is removed from the
+            // ConnectionService before Telecom sends the create conference complete callback.
+            Log.w(this, "notifyCreateConferenceComplete: callId is null.");
+            return;
+        }
+        onCreateConferenceComplete(findConferenceForAction(callId,
+                "notifyCreateConferenceComplete"));
+    }
+
+
     private void abort(String callId) {
         Log.d(this, "abort %s", callId);
         findConnectionForAction(callId, "abort").onAbort();
@@ -1676,12 +1946,20 @@
 
     private void answerVideo(String callId, int videoState) {
         Log.d(this, "answerVideo %s", callId);
-        findConnectionForAction(callId, "answer").onAnswer(videoState);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "answer").onAnswer(videoState);
+        } else {
+            findConferenceForAction(callId, "answer").onAnswer(videoState);
+        }
     }
 
     private void answer(String callId) {
         Log.d(this, "answer %s", callId);
-        findConnectionForAction(callId, "answer").onAnswer();
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "answer").onAnswer();
+        } else {
+            findConferenceForAction(callId, "answer").onAnswer();
+        }
     }
 
     private void deflect(String callId, Uri address) {
@@ -1691,7 +1969,11 @@
 
     private void reject(String callId) {
         Log.d(this, "reject %s", callId);
-        findConnectionForAction(callId, "reject").onReject();
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "reject").onReject();
+        } else {
+            findConferenceForAction(callId, "reject").onReject();
+        }
     }
 
     private void reject(String callId, String rejectWithMessage) {
@@ -2198,6 +2480,21 @@
             ConnectionRequest request) {
         return null;
     }
+    /**
+     * Create a {@code Connection} given an incoming request. This is used to attach to existing
+     * incoming conference call.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming call.
+     * @return The {@code Connection} object to satisfy this call, or {@code null} to
+     *         not handle the call.
+     */
+    public @Nullable Conference onCreateIncomingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return null;
+    }
 
     /**
      * Called after the {@link Connection} returned by
@@ -2212,6 +2509,19 @@
     }
 
     /**
+     * Called after the {@link Conference} returned by
+     * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)}
+     * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been
+     * added to the {@link ConnectionService} and sent to Telecom.
+     *
+     * @param conference the {@link Conference}.
+     * @hide
+     */
+    public void onCreateConferenceComplete(Conference conference) {
+    }
+
+
+    /**
      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
      * incoming {@link Connection} was denied.
      * <p>
@@ -2250,6 +2560,47 @@
     }
 
     /**
+     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+     * incoming {@link Conference} was denied.
+     * <p>
+     * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
+     * {@link Conference}, but Telecom has determined that the call cannot be allowed at this time.
+     * The {@link ConnectionService} is responsible for silently rejecting the new incoming
+     * {@link Conference}.
+     * <p>
+     * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request The incoming connection request.
+     */
+    public void onCreateIncomingConferenceFailed(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+    }
+
+    /**
+     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+     * outgoing {@link Conference} was denied.
+     * <p>
+     * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
+     * {@link Conference}, but Telecom has determined that the call cannot be placed at this time.
+     * The {@link ConnectionService} is responisible for informing the user that the
+     * {@link Conference} cannot be made at this time.
+     * <p>
+     * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request The outgoing connection request.
+     */
+    public void onCreateOutgoingConferenceFailed(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+    }
+
+
+    /**
      * Trigger recalculate functinality for conference calls. This is used when a Telephony
      * Connection is part of a conference controller but is not yet added to Connection
      * Service and hence cannot be added to the conference call.
@@ -2289,6 +2640,36 @@
     }
 
     /**
+     * Create a {@code Conference} given an outgoing request. This is used to initiate new
+     * outgoing conference call.
+     *
+     * @param connectionManagerPhoneAccount The connection manager account to use for managing
+     *         this call.
+     *         <p>
+     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
+     *         has registered one or more {@code PhoneAccount}s having
+     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
+     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
+     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
+     *         making the connection.
+     *         <p>
+     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
+     *         being asked to make a direct connection. The
+     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
+     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
+     *         making the connection.
+     * @param request Details about the outgoing call.
+     * @return The {@code Conference} object to satisfy this call, or the result of an invocation
+     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
+     */
+    public @Nullable Conference onCreateOutgoingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return null;
+    }
+
+
+    /**
      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
      * outgoing handover {@link Connection}.
      * <p>
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 04e930c..8f27323 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -100,6 +100,19 @@
         }
     }
 
+    void handleCreateConferenceComplete(
+            String id,
+            ConnectionRequest request,
+            ParcelableConference conference) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleCreateConferenceComplete(id, request, conference,
+                        Log.getExternalSession());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /**
      * Sets a call's state to active (e.g., an ongoing call where two parties can actively
      * communicate).
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 60b2172..79ad51b 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -75,6 +75,7 @@
     private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34;
     private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
     private static final int MSG_SET_CONFERENCE_STATE = 36;
+    private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -103,6 +104,19 @@
                     }
                     break;
                 }
+                case MSG_HANDLE_CREATE_CONFERENCE_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.handleCreateConferenceComplete(
+                                (String) args.arg1,
+                                (ConnectionRequest) args.arg2,
+                                (ParcelableConference) args.arg3,
+                                null /*Session.Info*/);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 case MSG_SET_ACTIVE:
                     mDelegate.setActive((String) msg.obj, null /*Session.Info*/);
                     break;
@@ -366,6 +380,20 @@
         }
 
         @Override
+        public void handleCreateConferenceComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConference conference,
+                Session.Info sessionInfo) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = id;
+            args.arg2 = request;
+            args.arg3 = conference;
+            mHandler.obtainMessage(MSG_HANDLE_CREATE_CONFERENCE_COMPLETE, args).sendToTarget();
+        }
+
+
+        @Override
         public void setActive(String connectionId, Session.Info sessionInfo) {
             mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget();
         }
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index ede0594..90b69a3 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -47,6 +47,34 @@
     private final int mAddressPresentation;
     private final String mCallerDisplayName;
     private final int mCallerDisplayNamePresentation;
+    private DisconnectCause mDisconnectCause;
+    private boolean mRingbackRequested;
+
+    public ParcelableConference(
+            PhoneAccountHandle phoneAccount,
+            int state,
+            int connectionCapabilities,
+            int connectionProperties,
+            List<String> connectionIds,
+            IVideoProvider videoProvider,
+            int videoState,
+            long connectTimeMillis,
+            long connectElapsedTimeMillis,
+            StatusHints statusHints,
+            Bundle extras,
+            Uri address,
+            int addressPresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
+            DisconnectCause disconnectCause,
+            boolean ringbackRequested) {
+        this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds,
+                videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis,
+                statusHints, extras, address, addressPresentation, callerDisplayName,
+                callerDisplayNamePresentation);
+        mDisconnectCause = disconnectCause;
+        mRingbackRequested = ringbackRequested;
+    }
 
     public ParcelableConference(
             PhoneAccountHandle phoneAccount,
@@ -79,6 +107,8 @@
         mAddressPresentation = addressPresentation;
         mCallerDisplayName = callerDisplayName;
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+        mDisconnectCause = null;
+        mRingbackRequested = false;
     }
 
     @Override
@@ -100,6 +130,10 @@
                 .append(mVideoState)
                 .append(", VideoProvider: ")
                 .append(mVideoProvider)
+                .append(", isRingbackRequested: ")
+                .append(mRingbackRequested)
+                .append(", disconnectCause: ")
+                .append(mDisconnectCause)
                 .toString();
     }
 
@@ -151,6 +185,13 @@
         return mAddress;
     }
 
+    public final DisconnectCause getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
     public int getHandlePresentation() {
         return mAddressPresentation;
     }
@@ -177,11 +218,14 @@
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
+            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            boolean isRingbackRequested = source.readInt() == 1;
 
             return new ParcelableConference(phoneAccount, state, capabilities, properties,
                     connectionIds, videoCallProvider, videoState, connectTimeMillis,
                     connectElapsedTimeMillis, statusHints, extras, address, addressPresentation,
-                    callerDisplayName, callerDisplayNamePresentation);
+                    callerDisplayName, callerDisplayNamePresentation, disconnectCause,
+                    isRingbackRequested);
         }
 
         @Override
@@ -215,5 +259,7 @@
         destination.writeInt(mAddressPresentation);
         destination.writeString(mCallerDisplayName);
         destination.writeInt(mCallerDisplayNamePresentation);
+        destination.writeParcelable(mDisconnectCause, 0);
+        destination.writeInt(mRingbackRequested ? 1 : 0);
     }
 }
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index bb858cb..abb210f 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -331,7 +331,17 @@
      */
     public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000;
 
-    /* NEXT CAPABILITY: 0x4000 */
+    /**
+     * An adhoc conference call is established by providing a list of addresses to
+     * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the
+     * {@link ConnectionService} is responsible for connecting all indicated participants
+     * to a conference simultaneously.
+     * This is in contrast to conferences formed by merging calls together (e.g. using
+     * {@link android.telecom.Call#mergeConference()}).
+     */
+    public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000;
+
+    /* NEXT CAPABILITY: 0x8000 */
 
     /**
      * URI scheme for telephone number URIs.
@@ -1054,6 +1064,9 @@
         if (hasCapabilities(CAPABILITY_RTT)) {
             sb.append("Rtt");
         }
+        if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) {
+            sb.append("AdhocConf");
+        }
         return sb.toString();
     }
 
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 1e73bd6..76640e0 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -101,6 +101,14 @@
         }
 
         @Override
+        public void handleCreateConferenceComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConference parcel,
+                Session.Info info) {
+        }
+
+        @Override
         public void setActive(String callId, Session.Info sessionInfo) {
             if (mConnectionById.containsKey(callId)) {
                 findConnectionForAction(callId, "setActive")
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af3c55a..68f5bf6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1792,6 +1792,45 @@
     }
 
     /**
+     * Registers a new incoming conference. A {@link ConnectionService} should invoke this method
+     * when it has an incoming conference. For managed {@link ConnectionService}s, the specified
+     * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+     * the user must have enabled the corresponding {@link PhoneAccount}.  This can be checked using
+     * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to add a new incoming call.
+     * <p>
+     * The incoming conference you are adding is assumed to have a video state of
+     * {@link VideoProfile#STATE_AUDIO_ONLY}, unless the extra value
+     * {@link #EXTRA_INCOMING_VIDEO_STATE} is specified.
+     * <p>
+     * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+     * associated with the {@link PhoneAccountHandle} and request additional information about the
+     * call (See {@link ConnectionService#onCreateIncomingConference}) before starting the incoming
+     * call UI.
+     * <p>
+     * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+     * the {@link PhoneAccountHandle} does not correspond to a registered {@link PhoneAccount} or
+     * the associated {@link PhoneAccount} is not currently enabled by the user.
+     *
+     * @param phoneAccount A {@link PhoneAccountHandle} registered with
+     *            {@link #registerPhoneAccount}.
+     * @param extras A bundle that will be passed through to
+     *            {@link ConnectionService#onCreateIncomingConference}.
+     */
+
+    public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount,
+            @NonNull Bundle extras) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().addNewIncomingConference(
+                        phoneAccount, extras == null ? new Bundle() : extras);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException adding a new incoming conference: " + phoneAccount, e);
+        }
+    }
+
+    /**
      * Registers a new unknown call with Telecom. This can only be called by the system Telephony
      * service. This is invoked when Telephony detects a new unknown connection that was neither
      * a new incoming call, nor an user-initiated outgoing call.
@@ -1995,6 +2034,42 @@
         }
     }
 
+
+    /**
+     * Place a new conference call with the provided participants using the system telecom service
+     * This method doesn't support placing of emergency calls.
+     *
+     * An adhoc conference call is established by providing a list of addresses to
+     * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the
+     * {@link ConnectionService} is responsible for connecting all indicated participants
+     * to a conference simultaneously.
+     * This is in contrast to conferences formed by merging calls together (e.g. using
+     * {@link android.telecom.Call#mergeConference()}).
+     *
+     * The following keys are supported in the supplied extras.
+     * <ul>
+     *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
+     *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
+     *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+     * </ul>
+     *
+     * @param participants List of participants to start conference with
+     * @param extras Bundle of extras to use with the call
+     */
+    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
+    public void startConference(@NonNull List<Uri> participants,
+            @NonNull Bundle extras) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.startConference(participants, extras,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#placeCall", e);
+            }
+        }
+    }
+
     /**
      * Enables and disables specified phone account.
      *