Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f43e1ec8 authored by Tyler Gunn's avatar Tyler Gunn Committed by Steve Kondik
Browse files

Creating connections for conference event package participants.

- Add "addExistingConnection" method to connection service which provides
a way for a connection service to notify telecom of a pre-existing
connection (connections are normally created through telecom).
- Modify TelephonyConferenceController to retrieve its state from a
multiparty connection in the conference (in the case of IMS calls, this
would be the ImsCall that manages the conference) instead of just taking
the first one.

Bug: 18057361
Change-Id: I26993aec54ecb0ce90ae6983fd3eed9d8d0a5773

Adding method to get disconnect cause from conference

Bug: 17842499
Change-Id: Ifb6e61d50d66aa8e8299e7024b2ab6d1d0f9d878

Add functions to update on conference changes.

+ Add onConferenceChanged() to Connection, so that the Connection can
make act on changes to the conference.
+ Add a utility function to PhoneCapabilities to remove a capability
from a PhoneCapabilities bitmask. Figure this abstracts some bit
logic away to make things more readable.

Bug: 17429707
Change-Id: I0c97731a95dd6573488cd10f094a63abe2428bce

Support disconnecting conference participants from conference.

- Added "onDisconnectConferenceParticipant" method used to inform a
connection acting as the conference host that one of the participants
should be disconnected.
- Added "VIRTUAL_CALL" PhoneCapability used to indicate that a call cannot
be the active or background call, but only be a child of another call.

Bug: 18228141
Bug: 18176606
Change-Id: I7aec631cc89c0f08e174b24ce3a9cb547b47e36b

Replace onConferenceChanged with onConnectionAdded.

This change is motivated by a desire to be able to adjust the
phone capabilities of a conference depending on its connections.

Turns out Connection is not a good place to do this, because when
the conference is set, this code is only fired if the connection
service already contains the conference. Conference makes more
sense to do this anyways, at the end of the day.

+ Add onConnectionAdded function to Conference.java.
- Remove onConferenceChanged function from Connection.java.

Bug: 18241753
Change-Id: Ib9212a8483a7a7b542618697399bbab8b2701b04

Change access to removeConnection so we can override it.

Bug: 18056632
Bug: 18240234
Change-Id: I4e82051a27f3565e9a854df8b27c35300cdbe2ad

Add functions to update on conference changes.

+ Add onConferenceChanged() to Connection, so that the Connection can
make act on changes to the conference.
+ Add a utility function to PhoneCapabilities to remove a capability
from a PhoneCapabilities bitmask. Figure this abstracts some bit
logic away to make things more readable.

Bug: 17429707
Change-Id: I0c97731a95dd6573488cd10f094a63abe2428bce

Add helper method for checking phone capabilities.

I'm figuring this can help standardize phone capability checks when
need to do all over the place.

Bug: 17429707
Change-Id: I97327fd08158de3c18d186b6092597a1d4abcf14

Conference event package performance improvement.

- Instead of sending each participant to the telephony conference
controller, all participants are sent at once.  This way the conference
only needs to be recalculated once.

Bug: 18057361
Change-Id: I86205fc7f1d2648bb180fc7eaf3ad611955952f9

Make add-call a global property of telecom. (1/4)

ADD_CALL didn't make sense as a property of Connection or Call.
This changes it to be a global property instead.

Bug: 18285352
Change-Id: I658e7a6977a848600272cde2914612c8691bb801

Add telephony.DisconnectCause for merged IMS call.

When merged, IMS calls are consolidated into one call. Thus they
"disconnect", even though the user can continue to comminucate
with the party on that call.

We want to recognize this scenario so we can control relevant
behaviors (ie.don't play the "end call" tone in this case).

Bug: 18291234
Change-Id: I93247f88ed74467470504bec88fcfb7ed714f0ff

Change Connections to allow setting conferenceable with conferences.

- Added IConferenceable interface used so that connections and conferences
can both be considered candidates as "conferenceable" with a connection.
- Fixed ConnectionService#conference to support cases where either call 1
or call 2 is a conference and the other is a connection.  Previously did
not support cases where call 2 is a conference.

Bug: 18200934
Change-Id: I32a8dd30a154d6280f2ae89fd147817235998465

Add GENERIC_CONFERENCE to phone capabilities.

This is a temporary capability to recognize CDMA conference calls
so we can show the right strings in the InCallUi. This should be
moved to CallProperties when possible. b/18434985

Bug: 18284408
Change-Id: Ia3e2ff91c8f8a91ff0123df4d3e6a581bdf85895

Propagating isMultiParty indicator from RIL up into Telephony layer.

- Adding isMultiParty() method to IImsCallSession AIDL.

Bug: 18058253
Change-Id: I1c44d3db0f7aa00a3ae6efb1231c5ddd39ab20ac

IMS call merge call-back changes.

Renaming callSessionMerged to callSessionMergeStarted.
Adding callSessionMergeComplete callback for when merge successfully
completes.

Bug: 18056632
Change-Id: I8fc73b6c9db5e7970de81d8c3e09abc80f1a262f

Communicating participant changes to conference controller.

- Add new ConferenceParticipant parcelable class which represents a
single participant contained in the conference event package.
- Adding callbacks/listeners to Connection to handle changes to
participant state.

Bug: 18057361
Change-Id: Iadfebe84959f30f8e835f282aa994c0b92768aa6

IMS: Add support for HD icon display related information

Added support to pass the restrict cause associated with the peer
in order to enable display of the HD icon when necessary.Added
support to pass local audio codec information and defined values
for new codecs not defined.

Change-Id: If05d46bdb3907a0ec7efdde48337a7932a7a5c21

Change-Id: Iadfebe84959f30f8e835f282aa994c0b92768aa6
parent a5c5c550
Loading
Loading
Loading
Loading
+29 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
 * @hide
 */
@SystemApi
public abstract class Conference {
public abstract class Conference implements IConferenceable {

    /** @hide */
    public abstract static class Listener {
@@ -182,6 +182,13 @@ public abstract class Conference {
     */
    public void onAudioStateChanged(AudioState state) {}

    /**
     * Notifies this conference that a connection has been added to it.
     *
     * @param connection The newly added connection.
     */
    public void onConnectionAdded(Connection connection) {}

    /**
     * Sets state to be on hold.
     */
@@ -210,6 +217,13 @@ public abstract class Conference {
        }
    }

    /**
     * @return The {@link DisconnectCause} for this connection.
     */
    public final DisconnectCause getDisconnectCause() {
        return mDisconnectCause;
    }

    /**
     * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
     *
@@ -235,6 +249,7 @@ public abstract class Conference {
        if (connection != null && !mChildConnections.contains(connection)) {
            if (connection.setConference(this)) {
                mChildConnections.add(connection);
                onConnectionAdded(connection);
                for (Listener l : mListeners) {
                    l.onConnectionAdded(this, connection);
                }
@@ -337,6 +352,19 @@ public abstract class Conference {
        return this;
    }

    /**
     * Retrieves the primary connection associated with the conference.  The primary connection is
     * the connection from which the conference will retrieve its current state.
     *
     * @return The primary connection.
     */
    public Connection getPrimaryConnection() {
        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
            return null;
        }
        return mUnmodifiableChildConnections.get(0);
    }

    /**
     * Inform this Conference that the state of its audio output has been changed externally.
     *
+22 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package android.telecom;

/**
 * {@hide}
 */
parcelable ConferenceParticipant;
+158 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package android.telecom;

import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * Parcelable representation of a participant's state in a conference call.
 * @hide
 */
public class ConferenceParticipant implements Parcelable {

    /**
     * The conference participant's handle (e.g., phone number).
     */
    private final Uri mHandle;

    /**
     * The display name for the participant.
     */
    private final String mDisplayName;

    /**
     * The endpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
     * conference call, this is the endpoint URI for the participant on the IMS conference server.
     */
    private final Uri mEndpoint;

    /**
     * The state of the participant in the conference.
     *
     * @see android.telecom.Connection
     */
    private final int mState;

    /**
     * Creates an instance of {@code ConferenceParticipant}.
     *
     * @param handle      The conference participant's handle (e.g., phone number).
     * @param displayName The display name for the participant.
     * @param endpoint    The enpoint Uri which uniquely identifies this conference participant.
     * @param state       The state of the participant in the conference.
     */
    public ConferenceParticipant(Uri handle, String displayName, Uri endpoint, int state) {
        mHandle = handle;
        mDisplayName = displayName;
        mEndpoint = endpoint;
        mState = state;
    }

    /**
     * Responsible for creating {@code ConferenceParticipant} objects for deserialized Parcels.
     */
    public static final Parcelable.Creator<ConferenceParticipant> CREATOR =
            new Parcelable.Creator<ConferenceParticipant>() {

                @Override
                public ConferenceParticipant createFromParcel(Parcel source) {
                    ClassLoader classLoader = ParcelableCall.class.getClassLoader();
                    Uri handle = source.readParcelable(classLoader);
                    String displayName = source.readString();
                    Uri endpoint = source.readParcelable(classLoader);
                    int state = source.readInt();
                    return new ConferenceParticipant(handle, displayName, endpoint, state);
                }

                @Override
                public ConferenceParticipant[] newArray(int size) {
                    return new ConferenceParticipant[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Writes the {@code ConferenceParticipant} to a parcel.
     *
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(mHandle, 0);
        dest.writeString(mDisplayName);
        dest.writeParcelable(mEndpoint, 0);
        dest.writeInt(mState);
    }

    /**
     * Builds a string representation of this instance.
     *
     * @return String representing the conference participant.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[ConferenceParticipant Handle: ");
        sb.append(mHandle);
        sb.append(" DisplayName: ");
        sb.append(mDisplayName);
        sb.append(" Endpoint: ");
        sb.append(mEndpoint);
        sb.append(" State: ");
        sb.append(mState);
        sb.append("]");
        return sb.toString();
    }

    /**
     * The conference participant's handle (e.g., phone number).
     */
    public Uri getHandle() {
        return mHandle;
    }

    /**
     * The display name for the participant.
     */
    public String getDisplayName() {
        return mDisplayName;
    }

    /**
     * The enpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
     * conference call, this is the endpoint URI for the participant on the IMS conference server.
     */
    public Uri getEndpoint() {
        return mEndpoint;
    }

    /**
     * The state of the participant in the conference.
     *
     * @see android.telecom.Connection
     */
    public int getState() {
        return mState;
    }
}
+87 −16
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ import java.util.concurrent.ConcurrentHashMap;
 * @hide
 */
@SystemApi
public abstract class Connection {
public abstract class Connection implements IConferenceable {

    private static final boolean DBG = false;

@@ -106,11 +106,14 @@ public abstract class Connection {
                Connection c, VideoProvider videoProvider) {}
        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
        public void onConferenceableConnectionsChanged(
                Connection c, List<Connection> conferenceableConnections) {}
        public void onConferenceablesChanged(
                Connection c, List<IConferenceable> conferenceables) {}
        public void onConferenceChanged(Connection c, Conference conference) {}
        public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {}
        public void onCallSubstateChanged(Connection c, int substate) {}
        /** @hide */
        public void onConferenceParticipantsChanged(Connection c,
                List<ConferenceParticipant> participants) {}
    }

    /** @hide */
@@ -492,7 +495,16 @@ public abstract class Connection {
    private final Listener mConnectionDeathListener = new Listener() {
        @Override
        public void onDestroyed(Connection c) {
            if (mConferenceableConnections.remove(c)) {
            if (mConferenceables.remove(c)) {
                fireOnConferenceableConnectionsChanged();
            }
        }
    };

    private final Conference.Listener mConferenceDeathListener = new Conference.Listener() {
        @Override
        public void onDestroyed(Conference c) {
            if (mConferenceables.remove(c)) {
                fireOnConferenceableConnectionsChanged();
            }
        }
@@ -505,9 +517,9 @@ public abstract class Connection {
     */
    private final Set<Listener> mListeners = Collections.newSetFromMap(
            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
    private final List<Connection> mConferenceableConnections = new ArrayList<>();
    private final List<Connection> mUnmodifiableConferenceableConnections =
            Collections.unmodifiableList(mConferenceableConnections);
    private final List<IConferenceable> mConferenceables = new ArrayList<>();
    private final List<IConferenceable> mUnmodifiableConferenceables =
            Collections.unmodifiableList(mConferenceables);

    private int mState = STATE_NEW;
    private AudioState mAudioState;
@@ -978,19 +990,44 @@ public abstract class Connection {
        for (Connection c : conferenceableConnections) {
            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
            // small amount of items here.
            if (!mConferenceableConnections.contains(c)) {
            if (!mConferenceables.contains(c)) {
                c.addConnectionListener(mConnectionDeathListener);
                mConferenceableConnections.add(c);
                mConferenceables.add(c);
            }
        }
        fireOnConferenceableConnectionsChanged();
    }

    /**
     * Similar to {@link #setConferenceableConnections(java.util.List)}, sets a list of connections
     * or conferences with which this connection can be conferenced.
     *
     * @param conferenceables The conferenceables.
     */
    public final void setConferenceables(List<IConferenceable> conferenceables) {
        clearConferenceableList();
        for (IConferenceable c : conferenceables) {
            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
            // small amount of items here.
            if (!mConferenceables.contains(c)) {
                if (c instanceof Connection) {
                    Connection connection = (Connection) c;
                    connection.addConnectionListener(mConnectionDeathListener);
                } else if (c instanceof Conference) {
                    Conference conference = (Conference) c;
                    conference.addListener(mConferenceDeathListener);
                }
                mConferenceables.add(c);
            }
        }
        fireOnConferenceableConnectionsChanged();
    }

    /**
     * Returns the connections with which this connection can be conferenced.
     * Returns the connections or conferences with which this connection can be conferenced.
     */
    public final List<Connection> getConferenceableConnections() {
        return mUnmodifiableConferenceableConnections;
    public final List<IConferenceable> getConferenceables() {
        return mUnmodifiableConferenceables;
    }

    /**
@@ -1055,6 +1092,7 @@ public abstract class Connection {
            mConference = conference;
            if (mConnectionService != null && mConnectionService.containsConference(conference)) {
                fireConferenceChanged();
                onConferenceChanged();
            }
            return true;
        }
@@ -1117,6 +1155,15 @@ public abstract class Connection {
     */
    public void onDisconnect() {}

    /**
     * Notifies this Connection of a request to disconnect a participant of the conference managed
     * by the connection.
     *
     * @param endpoint the {@link Uri} of the participant to disconnect.
     * @hide
     */
    public void onDisconnectConferenceParticipant(Uri endpoint) {}

    /**
     * Notifies this Connection of a request to separate from its parent conference.
     */
@@ -1182,6 +1229,11 @@ public abstract class Connection {
     */
    public void onConferenceWith(Connection otherConnection) {}

    /**
     * Notifies this Connection that the conference which is set on it has changed.
     */
    public void onConferenceChanged() {}

    static String toLogSafePhoneNumber(String number) {
        // For unknown number, log empty string.
        if (number == null) {
@@ -1260,7 +1312,7 @@ public abstract class Connection {

    private final void  fireOnConferenceableConnectionsChanged() {
        for (Listener l : mListeners) {
            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
            l.onConferenceablesChanged(this, getConferenceables());
        }
    }

@@ -1271,9 +1323,28 @@ public abstract class Connection {
    }

    private final void clearConferenceableList() {
        for (Connection c : mConferenceableConnections) {
            c.removeConnectionListener(mConnectionDeathListener);
        for (IConferenceable c : mConferenceables) {
            if (c instanceof Connection) {
                Connection connection = (Connection) c;
                connection.removeConnectionListener(mConnectionDeathListener);
            } else if (c instanceof Conference) {
                Conference conference = (Conference) c;
                conference.removeListener(mConferenceDeathListener);
            }
        }
        mConferenceables.clear();
    }

    /**
     * Notifies listeners of a change to conference participant(s).
     *
     * @param conferenceParticipants The participants.
     * @hide
     */
    protected final void updateConferenceParticipants(
            List<ConferenceParticipant> conferenceParticipants) {
        for (Listener l : mListeners) {
            l.onConferenceParticipantsChanged(this, conferenceParticipants);
        }
        mConferenceableConnections.clear();
    }
}
+118 −9
Original line number Diff line number Diff line
@@ -552,11 +552,11 @@ public abstract class ConnectionService extends Service {
        }

        @Override
        public void onConferenceableConnectionsChanged(
                Connection connection, List<Connection> conferenceableConnections) {
        public void onConferenceablesChanged(
                Connection connection, List<IConferenceable> conferenceables) {
            mAdapter.setConferenceableConnections(
                    mIdByConnection.get(connection),
                    createConnectionIdList(conferenceableConnections));
                    createIdList(conferenceables));
        }

        @Override
@@ -655,7 +655,7 @@ public abstract class ConnectionService extends Service {
                        connection.getAudioModeIsVoip(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createConnectionIdList(connection.getConferenceableConnections()),
                        createIdList(connection.getConferenceables()),
                        connection.getCallSubstate()));
    }

@@ -763,12 +763,19 @@ public abstract class ConnectionService extends Service {
    private void conference(String callId1, String callId2) {
        Log.d(this, "conference %s, %s", callId1, callId2);

        // Attempt to get second connection or conference.
        Connection connection2 = findConnectionForAction(callId2, "conference");
        Conference conference2 = getNullConference();
        if (connection2 == getNullConnection()) {
            Log.w(this, "Connection2 missing in conference request %s.", callId2);
            conference2 = findConferenceForAction(callId2, "conference");
            if (conference2 == getNullConference()) {
                Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
                        callId2);
                return;
            }
        }

        // Attempt to get first connection or conference and perform merge.
        Connection connection1 = findConnectionForAction(callId1, "conference");
        if (connection1 == getNullConnection()) {
            Conference conference1 = findConferenceForAction(callId1, "addConnection");
@@ -777,12 +784,28 @@ public abstract class ConnectionService extends Service {
                        "Connection1 or Conference1 missing in conference request %s.",
                        callId1);
            } else {
                // Call 1 is a conference.
                if (connection2 != getNullConnection()) {
                    // Call 2 is a connection so merge via call 1 (conference).
                    conference1.onMerge(connection2);
                } else {
                    // Call 2 is ALSO a conference; this should never happen.
                    Log.wtf(this, "There can only be one conference and an attempt was made to " +
                            "merge two conferences.");
                    return;
                }
            }
        } else {
            // Call 1 is a connection.
            if (conference2 != getNullConference()) {
                // Call 2 is a conference, so merge via call 2.
                conference2.onMerge(connection1);
            } else {
                // Call 2 is a connection, so merge together.
                onConference(connection1, connection2);
            }
        }
    }

    private void splitFromConference(String callId) {
        Log.d(this, "splitFromConference(%s)", callId);
@@ -934,6 +957,41 @@ public abstract class ConnectionService extends Service {
        }
    }

    /**
     * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
     * connection.
     *
     * @param phoneAccountHandle The phone account handle for the connection.
     * @param connection The connection to add.
     */
    public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
            Connection connection) {

        String id = addExistingConnectionInternal(connection);
        if (id != null) {
            List<String> emptyList = new ArrayList<>(0);

            ParcelableConnection parcelableConnection = new ParcelableConnection(
                    phoneAccountHandle,
                    connection.getState(),
                    connection.getCallCapabilities(),
                    connection.getAddress(),
                    connection.getAddressPresentation(),
                    connection.getCallerDisplayName(),
                    connection.getCallerDisplayNamePresentation(),
                    connection.getVideoProvider() == null ?
                            null : connection.getVideoProvider().getInterface(),
                    connection.getVideoState(),
                    connection.isRingbackRequested(),
                    connection.getAudioModeIsVoip(),
                    connection.getStatusHints(),
                    connection.getDisconnectCause(),
                    emptyList,
                    connection.getCallSubstate());
            mAdapter.addExistingConnection(id, parcelableConnection);
        }
    }

    /**
     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
     * has taken responsibility.
@@ -1018,6 +1076,12 @@ public abstract class ConnectionService extends Service {

    public void onRemoteConferenceAdded(RemoteConference conference) {}

    /**
     * Called when an existing connection is added remotely.
     * @param connection The existing connection which was added.
     */
    public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}

    /**
     * @hide
     */
@@ -1030,6 +1094,11 @@ public abstract class ConnectionService extends Service {
        onRemoteConferenceAdded(remoteConference);
    }

    /** {@hide} */
    void addRemoteExistingConnection(RemoteConnection remoteConnection) {
        onRemoteExistingConnectionAdded(remoteConnection);
    }

    private void onAccountsInitialized() {
        mAreAccountsInitialized = true;
        for (Runnable r : mPreInitializationConnectionRequests) {
@@ -1038,6 +1107,18 @@ public abstract class ConnectionService extends Service {
        mPreInitializationConnectionRequests.clear();
    }

    /**
     * Adds an existing connection to the list of connections, identified by a new UUID.
     *
     * @param connection The connection.
     * @return The UUID of the connection (e.g. the call-id).
     */
    private String addExistingConnectionInternal(Connection connection) {
        String id = UUID.randomUUID().toString();
        addConnection(id, connection);
        return id;
    }

    private void addConnection(String callId, Connection connection) {
        mConnectionById.put(callId, connection);
        mIdByConnection.put(connection, callId);
@@ -1045,7 +1126,8 @@ public abstract class ConnectionService extends Service {
        connection.setConnectionService(this);
    }

    private void removeConnection(Connection connection) {
    /** {@hide} */
    protected void removeConnection(Connection connection) {
        String id = mIdByConnection.get(connection);
        connection.unsetConnectionService(this);
        connection.removeConnectionListener(mConnectionListener);
@@ -1113,6 +1195,33 @@ public abstract class ConnectionService extends Service {
        return ids;
    }

    /**
     * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
     * {@link IConferenceable}s passed in.
     *
     * @param conferenceables The {@link IConferenceable} connections and conferences.
     * @return List of string conference and call Ids.
     */
    private List<String> createIdList(List<IConferenceable> conferenceables) {
        List<String> ids = new ArrayList<>();
        for (IConferenceable c : conferenceables) {
            // Only allow Connection and Conference conferenceables.
            if (c instanceof Connection) {
                Connection connection = (Connection) c;
                if (mIdByConnection.containsKey(connection)) {
                    ids.add(mIdByConnection.get(connection));
                }
            } else if (c instanceof Conference) {
                Conference conference = (Conference) c;
                if (mIdByConference.containsKey(conference)) {
                    ids.add(mIdByConference.get(conference));
                }
            }
        }
        Collections.sort(ids);
        return ids;
    }

    private Conference getNullConference() {
        if (sNullConference == null) {
            sNullConference = new Conference(null) {};
Loading