Conference.java revision 46f7f5dce42d645353a0f3eb0dbdd25b3a6c72fb
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telecom;
18
19import java.util.ArrayList;
20import java.util.Collections;
21import java.util.List;
22import java.util.Set;
23import java.util.concurrent.CopyOnWriteArrayList;
24import java.util.concurrent.CopyOnWriteArraySet;
25
26/**
27 * Represents a conference call which can contain any number of {@link Connection} objects.
28 */
29public abstract class Conference {
30
31    /** @hide */
32    public abstract static class Listener {
33        public void onStateChanged(Conference conference, int oldState, int newState) {}
34        public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
35        public void onConnectionAdded(Conference conference, Connection connection) {}
36        public void onConnectionRemoved(Conference conference, Connection connection) {}
37        public void onConferenceableConnectionsChanged(
38                Conference conference, List<Connection> conferenceableConnections) {}
39        public void onDestroyed(Conference conference) {}
40        public void onCapabilitiesChanged(Conference conference, int capabilities) {}
41    }
42
43    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
44    private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
45    private final List<Connection> mUnmodifiableChildConnections =
46            Collections.unmodifiableList(mChildConnections);
47    private final List<Connection> mConferenceableConnections = new ArrayList<>();
48    private final List<Connection> mUnmodifiableConferenceableConnections =
49            Collections.unmodifiableList(mConferenceableConnections);
50
51    private PhoneAccountHandle mPhoneAccount;
52    private AudioState mAudioState;
53    private int mState = Connection.STATE_NEW;
54    private DisconnectCause mDisconnectCause;
55    private int mCapabilities;
56    private String mDisconnectMessage;
57
58    private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
59        @Override
60        public void onDestroyed(Connection c) {
61            if (mConferenceableConnections.remove(c)) {
62                fireOnConferenceableConnectionsChanged();
63            }
64        }
65    };
66
67    /**
68     * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
69     *
70     * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
71     */
72    public Conference(PhoneAccountHandle phoneAccount) {
73        mPhoneAccount = phoneAccount;
74    }
75
76    /**
77     * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
78     *
79     * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
80     */
81    public final PhoneAccountHandle getPhoneAccountHandle() {
82        return mPhoneAccount;
83    }
84
85    /**
86     * Returns the list of connections currently associated with the conference call.
87     *
88     * @return A list of {@code Connection} objects which represent the children of the conference.
89     */
90    public final List<Connection> getConnections() {
91        return mUnmodifiableChildConnections;
92    }
93
94    /**
95     * Gets the state of the conference call. See {@link Connection} for valid values.
96     *
97     * @return A constant representing the state the conference call is currently in.
98     */
99    public final int getState() {
100        return mState;
101    }
102
103    /**
104     * Returns the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
105     *
106     * @return A bitmask of the {@code PhoneCapabilities} of the conference call.
107     */
108    public final int getCapabilities() {
109        return mCapabilities;
110    }
111
112    /**
113     * @return The audio state of the conference, describing how its audio is currently
114     *         being routed by the system. This is {@code null} if this Conference
115     *         does not directly know about its audio state.
116     */
117    public final AudioState getAudioState() {
118        return mAudioState;
119    }
120
121    /**
122     * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
123     */
124    public void onDisconnect() {}
125
126    /**
127     * Invoked when the specified {@link Connection} should be separated from the conference call.
128     *
129     * @param connection The connection to separate.
130     */
131    public void onSeparate(Connection connection) {}
132
133    /**
134     * Invoked when the specified {@link Connection} should merged with the conference call.
135     *
136     * @param connection The {@code Connection} to merge.
137     */
138    public void onMerge(Connection connection) {}
139
140    /**
141     * Invoked when the conference should be put on hold.
142     */
143    public void onHold() {}
144
145    /**
146     * Invoked when the conference should be moved from hold to active.
147     */
148    public void onUnhold() {}
149
150    /**
151     * Invoked when the child calls should be merged. Only invoked if the conference contains the
152     * capability {@link PhoneCapabilities#MERGE_CONFERENCE}.
153     */
154    public void onMerge() {}
155
156    /**
157     * Invoked when the child calls should be swapped. Only invoked if the conference contains the
158     * capability {@link PhoneCapabilities#SWAP_CONFERENCE}.
159     */
160    public void onSwap() {}
161
162    /**
163     * Notifies this conference of a request to play a DTMF tone.
164     *
165     * @param c A DTMF character.
166     */
167    public void onPlayDtmfTone(char c) {}
168
169    /**
170     * Notifies this conference of a request to stop any currently playing DTMF tones.
171     */
172    public void onStopDtmfTone() {}
173
174    /**
175     * Notifies this conference that the {@link #getAudioState()} property has a new value.
176     *
177     * @param state The new call audio state.
178     */
179    public void onAudioStateChanged(AudioState state) {}
180
181    /**
182     * Notifies this conference that a connection has been added to it.
183     *
184     * @param connection The newly added connection.
185     */
186    public void onConnectionAdded(Connection connection) {}
187
188    /**
189     * Sets state to be on hold.
190     */
191    public final void setOnHold() {
192        setState(Connection.STATE_HOLDING);
193    }
194
195    /**
196     * Sets state to be active.
197     */
198    public final void setActive() {
199        setState(Connection.STATE_ACTIVE);
200    }
201
202    /**
203     * Sets state to disconnected.
204     *
205     * @param disconnectCause The reason for the disconnection, as described by
206     *     {@link android.telecom.DisconnectCause}.
207     */
208    public final void setDisconnected(DisconnectCause disconnectCause) {
209        mDisconnectCause = disconnectCause;;
210        setState(Connection.STATE_DISCONNECTED);
211        for (Listener l : mListeners) {
212            l.onDisconnected(this, mDisconnectCause);
213        }
214    }
215
216    /**
217     * @return The {@link DisconnectCause} for this connection.
218     */
219    public final DisconnectCause getDisconnectCause() {
220        return mDisconnectCause;
221    }
222
223    /**
224     * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
225     *
226     * @param capabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
227     */
228    public final void setCapabilities(int capabilities) {
229        if (capabilities != mCapabilities) {
230            mCapabilities = capabilities;
231
232            for (Listener l : mListeners) {
233                l.onCapabilitiesChanged(this, mCapabilities);
234            }
235        }
236    }
237
238    /**
239     * Adds the specified connection as a child of this conference.
240     *
241     * @param connection The connection to add.
242     * @return True if the connection was successfully added.
243     */
244    public final boolean addConnection(Connection connection) {
245        if (connection != null && !mChildConnections.contains(connection)) {
246            if (connection.setConference(this)) {
247                mChildConnections.add(connection);
248                onConnectionAdded(connection);
249                for (Listener l : mListeners) {
250                    l.onConnectionAdded(this, connection);
251                }
252                return true;
253            }
254        }
255        return false;
256    }
257
258    /**
259     * Removes the specified connection as a child of this conference.
260     *
261     * @param connection The connection to remove.
262     */
263    public final void removeConnection(Connection connection) {
264        Log.d(this, "removing %s from %s", connection, mChildConnections);
265        if (connection != null && mChildConnections.remove(connection)) {
266            connection.resetConference();
267            for (Listener l : mListeners) {
268                l.onConnectionRemoved(this, connection);
269            }
270        }
271    }
272
273    /**
274     * Sets the connections with which this connection can be conferenced.
275     *
276     * @param conferenceableConnections The set of connections this connection can conference with.
277     */
278    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
279        clearConferenceableList();
280        for (Connection c : conferenceableConnections) {
281            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
282            // small amount of items here.
283            if (!mConferenceableConnections.contains(c)) {
284                c.addConnectionListener(mConnectionDeathListener);
285                mConferenceableConnections.add(c);
286            }
287        }
288        fireOnConferenceableConnectionsChanged();
289    }
290
291    private final void fireOnConferenceableConnectionsChanged() {
292        for (Listener l : mListeners) {
293            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
294        }
295    }
296
297    /**
298     * Returns the connections with which this connection can be conferenced.
299     */
300    public final List<Connection> getConferenceableConnections() {
301        return mUnmodifiableConferenceableConnections;
302    }
303
304    /**
305     * Tears down the conference object and any of its current connections.
306     */
307    public final void destroy() {
308        Log.d(this, "destroying conference : %s", this);
309        // Tear down the children.
310        for (Connection connection : mChildConnections) {
311            Log.d(this, "removing connection %s", connection);
312            removeConnection(connection);
313        }
314
315        // If not yet disconnected, set the conference call as disconnected first.
316        if (mState != Connection.STATE_DISCONNECTED) {
317            Log.d(this, "setting to disconnected");
318            setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
319        }
320
321        // ...and notify.
322        for (Listener l : mListeners) {
323            l.onDestroyed(this);
324        }
325    }
326
327    /**
328     * Add a listener to be notified of a state change.
329     *
330     * @param listener The new listener.
331     * @return This conference.
332     * @hide
333     */
334    public final Conference addListener(Listener listener) {
335        mListeners.add(listener);
336        return this;
337    }
338
339    /**
340     * Removes the specified listener.
341     *
342     * @param listener The listener to remove.
343     * @return This conference.
344     * @hide
345     */
346    public final Conference removeListener(Listener listener) {
347        mListeners.remove(listener);
348        return this;
349    }
350
351    /**
352     * Retrieves the primary connection associated with the conference.  The primary connection is
353     * the connection from which the conference will retrieve its current state.
354     *
355     * @return The primary connection.
356     */
357    public Connection getPrimaryConnection() {
358        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
359            return null;
360        }
361        return mUnmodifiableChildConnections.get(0);
362    }
363
364    /**
365     * Inform this Conference that the state of its audio output has been changed externally.
366     *
367     * @param state The new audio state.
368     * @hide
369     */
370    final void setAudioState(AudioState state) {
371        Log.d(this, "setAudioState %s", state);
372        mAudioState = state;
373        onAudioStateChanged(state);
374    }
375
376    private void setState(int newState) {
377        if (newState != Connection.STATE_ACTIVE &&
378                newState != Connection.STATE_HOLDING &&
379                newState != Connection.STATE_DISCONNECTED) {
380            Log.w(this, "Unsupported state transition for Conference call.",
381                    Connection.stateToString(newState));
382            return;
383        }
384
385        if (mState != newState) {
386            int oldState = mState;
387            mState = newState;
388            for (Listener l : mListeners) {
389                l.onStateChanged(this, oldState, newState);
390            }
391        }
392    }
393
394    private final void clearConferenceableList() {
395        for (Connection c : mConferenceableConnections) {
396            c.removeConnectionListener(mConnectionDeathListener);
397        }
398        mConferenceableConnections.clear();
399    }
400}
401