Conference.java revision 4a57b9b59b74c97e559a301af0add13cd4c3331c
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     * Sets state to be on hold.
183     */
184    public final void setOnHold() {
185        setState(Connection.STATE_HOLDING);
186    }
187
188    /**
189     * Sets state to be active.
190     */
191    public final void setActive() {
192        setState(Connection.STATE_ACTIVE);
193    }
194
195    /**
196     * Sets state to disconnected.
197     *
198     * @param disconnectCause The reason for the disconnection, as described by
199     *     {@link android.telecom.DisconnectCause}.
200     */
201    public final void setDisconnected(DisconnectCause disconnectCause) {
202        mDisconnectCause = disconnectCause;;
203        setState(Connection.STATE_DISCONNECTED);
204        for (Listener l : mListeners) {
205            l.onDisconnected(this, mDisconnectCause);
206        }
207    }
208
209    /**
210     * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
211     *
212     * @param capabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
213     */
214    public final void setCapabilities(int capabilities) {
215        if (capabilities != mCapabilities) {
216            mCapabilities = capabilities;
217
218            for (Listener l : mListeners) {
219                l.onCapabilitiesChanged(this, mCapabilities);
220            }
221        }
222    }
223
224    /**
225     * Adds the specified connection as a child of this conference.
226     *
227     * @param connection The connection to add.
228     * @return True if the connection was successfully added.
229     */
230    public final boolean addConnection(Connection connection) {
231        if (connection != null && !mChildConnections.contains(connection)) {
232            if (connection.setConference(this)) {
233                mChildConnections.add(connection);
234                for (Listener l : mListeners) {
235                    l.onConnectionAdded(this, connection);
236                }
237                return true;
238            }
239        }
240        return false;
241    }
242
243    /**
244     * Removes the specified connection as a child of this conference.
245     *
246     * @param connection The connection to remove.
247     */
248    public final void removeConnection(Connection connection) {
249        Log.d(this, "removing %s from %s", connection, mChildConnections);
250        if (connection != null && mChildConnections.remove(connection)) {
251            connection.resetConference();
252            for (Listener l : mListeners) {
253                l.onConnectionRemoved(this, connection);
254            }
255        }
256    }
257
258    /**
259     * Sets the connections with which this connection can be conferenced.
260     *
261     * @param conferenceableConnections The set of connections this connection can conference with.
262     */
263    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
264        clearConferenceableList();
265        for (Connection c : conferenceableConnections) {
266            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
267            // small amount of items here.
268            if (!mConferenceableConnections.contains(c)) {
269                c.addConnectionListener(mConnectionDeathListener);
270                mConferenceableConnections.add(c);
271            }
272        }
273        fireOnConferenceableConnectionsChanged();
274    }
275
276    private final void fireOnConferenceableConnectionsChanged() {
277        for (Listener l : mListeners) {
278            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
279        }
280    }
281
282    /**
283     * Returns the connections with which this connection can be conferenced.
284     */
285    public final List<Connection> getConferenceableConnections() {
286        return mUnmodifiableConferenceableConnections;
287    }
288
289    /**
290     * Tears down the conference object and any of its current connections.
291     */
292    public final void destroy() {
293        Log.d(this, "destroying conference : %s", this);
294        // Tear down the children.
295        for (Connection connection : mChildConnections) {
296            Log.d(this, "removing connection %s", connection);
297            removeConnection(connection);
298        }
299
300        // If not yet disconnected, set the conference call as disconnected first.
301        if (mState != Connection.STATE_DISCONNECTED) {
302            Log.d(this, "setting to disconnected");
303            setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
304        }
305
306        // ...and notify.
307        for (Listener l : mListeners) {
308            l.onDestroyed(this);
309        }
310    }
311
312    /**
313     * Add a listener to be notified of a state change.
314     *
315     * @param listener The new listener.
316     * @return This conference.
317     * @hide
318     */
319    public final Conference addListener(Listener listener) {
320        mListeners.add(listener);
321        return this;
322    }
323
324    /**
325     * Removes the specified listener.
326     *
327     * @param listener The listener to remove.
328     * @return This conference.
329     * @hide
330     */
331    public final Conference removeListener(Listener listener) {
332        mListeners.remove(listener);
333        return this;
334    }
335
336    /**
337     * Retrieves the primary connection associated with the conference.  The primary connection is
338     * the connection from which the conference will retrieve its current state.
339     *
340     * @return The primary connection.
341     */
342    public Connection getPrimaryConnection() {
343        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
344            return null;
345        }
346        return mUnmodifiableChildConnections.get(0);
347    }
348
349    /**
350     * Inform this Conference that the state of its audio output has been changed externally.
351     *
352     * @param state The new audio state.
353     * @hide
354     */
355    final void setAudioState(AudioState state) {
356        Log.d(this, "setAudioState %s", state);
357        mAudioState = state;
358        onAudioStateChanged(state);
359    }
360
361    private void setState(int newState) {
362        if (newState != Connection.STATE_ACTIVE &&
363                newState != Connection.STATE_HOLDING &&
364                newState != Connection.STATE_DISCONNECTED) {
365            Log.w(this, "Unsupported state transition for Conference call.",
366                    Connection.stateToString(newState));
367            return;
368        }
369
370        if (mState != newState) {
371            int oldState = mState;
372            mState = newState;
373            for (Listener l : mListeners) {
374                l.onStateChanged(this, oldState, newState);
375            }
376        }
377    }
378
379    private final void clearConferenceableList() {
380        for (Connection c : mConferenceableConnections) {
381            c.removeConnectionListener(mConnectionDeathListener);
382        }
383        mConferenceableConnections.clear();
384    }
385}
386