Conference.java revision 1cf9b6bec12c027a0d551540a6e01f3ac2d0a9d4
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 android.telecom.Connection.VideoProvider;
20
21import java.util.ArrayList;
22import java.util.Collections;
23import java.util.List;
24import java.util.Locale;
25import java.util.Set;
26import java.util.concurrent.CopyOnWriteArrayList;
27import java.util.concurrent.CopyOnWriteArraySet;
28
29/**
30 * Represents a conference call which can contain any number of {@link Connection} objects.
31 */
32public abstract class Conference implements IConferenceable {
33
34    /**
35     * Used to indicate that the conference connection time is not specified.  If not specified,
36     * Telecom will set the connect time.
37     */
38    public static final long CONNECT_TIME_NOT_SPECIFIED = 0;
39
40    /** @hide */
41    public abstract static class Listener {
42        public void onStateChanged(Conference conference, int oldState, int newState) {}
43        public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
44        public void onConnectionAdded(Conference conference, Connection connection) {}
45        public void onConnectionRemoved(Conference conference, Connection connection) {}
46        public void onConferenceableConnectionsChanged(
47                Conference conference, List<Connection> conferenceableConnections) {}
48        public void onDestroyed(Conference conference) {}
49        public void onConnectionCapabilitiesChanged(
50                Conference conference, int connectionCapabilities) {}
51        public void onVideoStateChanged(Conference c, int videoState) { }
52        public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
53    }
54
55    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
56    private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
57    private final List<Connection> mUnmodifiableChildConnections =
58            Collections.unmodifiableList(mChildConnections);
59    private final List<Connection> mConferenceableConnections = new ArrayList<>();
60    private final List<Connection> mUnmodifiableConferenceableConnections =
61            Collections.unmodifiableList(mConferenceableConnections);
62
63    private PhoneAccountHandle mPhoneAccount;
64    private AudioState mAudioState;
65    private int mState = Connection.STATE_NEW;
66    private DisconnectCause mDisconnectCause;
67    private int mConnectionCapabilities;
68    private String mDisconnectMessage;
69    private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
70
71    private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
72        @Override
73        public void onDestroyed(Connection c) {
74            if (mConferenceableConnections.remove(c)) {
75                fireOnConferenceableConnectionsChanged();
76            }
77        }
78    };
79
80    /**
81     * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
82     *
83     * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
84     */
85    public Conference(PhoneAccountHandle phoneAccount) {
86        mPhoneAccount = phoneAccount;
87    }
88
89    /**
90     * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
91     *
92     * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
93     */
94    public final PhoneAccountHandle getPhoneAccountHandle() {
95        return mPhoneAccount;
96    }
97
98    /**
99     * Returns the list of connections currently associated with the conference call.
100     *
101     * @return A list of {@code Connection} objects which represent the children of the conference.
102     */
103    public final List<Connection> getConnections() {
104        return mUnmodifiableChildConnections;
105    }
106
107    /**
108     * Gets the state of the conference call. See {@link Connection} for valid values.
109     *
110     * @return A constant representing the state the conference call is currently in.
111     */
112    public final int getState() {
113        return mState;
114    }
115
116    /**
117     * Returns the capabilities of a conference. See {@code CAPABILITY_*} constants in class
118     * {@link Connection} for valid values.
119     *
120     * @return A bitmask of the capabilities of the conference call.
121     */
122    public final int getConnectionCapabilities() {
123        return mConnectionCapabilities;
124    }
125
126    /**
127     * Whether the given capabilities support the specified capability.
128     *
129     * @param capabilities A capability bit field.
130     * @param capability The capability to check capabilities for.
131     * @return Whether the specified capability is supported.
132     * @hide
133     */
134    public static boolean can(int capabilities, int capability) {
135        return (capabilities & capability) != 0;
136    }
137
138    /**
139     * Whether the capabilities of this {@code Connection} supports the specified capability.
140     *
141     * @param capability The capability to check capabilities for.
142     * @return Whether the specified capability is supported.
143     * @hide
144     */
145    public boolean can(int capability) {
146        return can(mConnectionCapabilities, capability);
147    }
148
149    /**
150     * Removes the specified capability from the set of capabilities of this {@code Conference}.
151     *
152     * @param capability The capability to remove from the set.
153     * @hide
154     */
155    public void removeCapability(int capability) {
156        mConnectionCapabilities &= ~capability;
157    }
158
159    /**
160     * Adds the specified capability to the set of capabilities of this {@code Conference}.
161     *
162     * @param capability The capability to add to the set.
163     * @hide
164     */
165    public void addCapability(int capability) {
166        mConnectionCapabilities |= capability;
167    }
168
169    /**
170     * @return The audio state of the conference, describing how its audio is currently
171     *         being routed by the system. This is {@code null} if this Conference
172     *         does not directly know about its audio state.
173     */
174    public final AudioState getAudioState() {
175        return mAudioState;
176    }
177
178    /**
179     * Returns VideoProvider of the primary call. This can be null.
180     *  @hide
181     */
182    public VideoProvider getVideoProvider() {
183        return null;
184    }
185
186    /**
187     * Returns video state of the primary call.
188     *  @hide
189     */
190    public int getVideoState() {
191        return VideoProfile.VideoState.AUDIO_ONLY;
192    }
193
194    /**
195     * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
196     */
197    public void onDisconnect() {}
198
199    /**
200     * Invoked when the specified {@link Connection} should be separated from the conference call.
201     *
202     * @param connection The connection to separate.
203     */
204    public void onSeparate(Connection connection) {}
205
206    /**
207     * Invoked when the specified {@link Connection} should merged with the conference call.
208     *
209     * @param connection The {@code Connection} to merge.
210     */
211    public void onMerge(Connection connection) {}
212
213    /**
214     * Invoked when the conference should be put on hold.
215     */
216    public void onHold() {}
217
218    /**
219     * Invoked when the conference should be moved from hold to active.
220     */
221    public void onUnhold() {}
222
223    /**
224     * Invoked when the child calls should be merged. Only invoked if the conference contains the
225     * capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
226     */
227    public void onMerge() {}
228
229    /**
230     * Invoked when the child calls should be swapped. Only invoked if the conference contains the
231     * capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
232     */
233    public void onSwap() {}
234
235    /**
236     * Notifies this conference of a request to play a DTMF tone.
237     *
238     * @param c A DTMF character.
239     */
240    public void onPlayDtmfTone(char c) {}
241
242    /**
243     * Notifies this conference of a request to stop any currently playing DTMF tones.
244     */
245    public void onStopDtmfTone() {}
246
247    /**
248     * Notifies this conference that the {@link #getAudioState()} property has a new value.
249     *
250     * @param state The new call audio state.
251     */
252    public void onAudioStateChanged(AudioState state) {}
253
254    /**
255     * Notifies this conference that a connection has been added to it.
256     *
257     * @param connection The newly added connection.
258     */
259    public void onConnectionAdded(Connection connection) {}
260
261    /**
262     * Sets state to be on hold.
263     */
264    public final void setOnHold() {
265        setState(Connection.STATE_HOLDING);
266    }
267
268    /**
269     * Sets state to be active.
270     */
271    public final void setActive() {
272        setState(Connection.STATE_ACTIVE);
273    }
274
275    /**
276     * Sets state to disconnected.
277     *
278     * @param disconnectCause The reason for the disconnection, as described by
279     *     {@link android.telecom.DisconnectCause}.
280     */
281    public final void setDisconnected(DisconnectCause disconnectCause) {
282        mDisconnectCause = disconnectCause;;
283        setState(Connection.STATE_DISCONNECTED);
284        for (Listener l : mListeners) {
285            l.onDisconnected(this, mDisconnectCause);
286        }
287    }
288
289    /**
290     * @return The {@link DisconnectCause} for this connection.
291     */
292    public final DisconnectCause getDisconnectCause() {
293        return mDisconnectCause;
294    }
295
296    /**
297     * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
298     * {@link Connection} for valid values.
299     *
300     * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
301     */
302    public final void setConnectionCapabilities(int connectionCapabilities) {
303        if (connectionCapabilities != mConnectionCapabilities) {
304            mConnectionCapabilities = connectionCapabilities;
305
306            for (Listener l : mListeners) {
307                l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
308            }
309        }
310    }
311
312    /**
313     * Adds the specified connection as a child of this conference.
314     *
315     * @param connection The connection to add.
316     * @return True if the connection was successfully added.
317     */
318    public final boolean addConnection(Connection connection) {
319        Log.d(this, "Connection=%s, connection=", connection);
320        if (connection != null && !mChildConnections.contains(connection)) {
321            if (connection.setConference(this)) {
322                mChildConnections.add(connection);
323                onConnectionAdded(connection);
324                for (Listener l : mListeners) {
325                    l.onConnectionAdded(this, connection);
326                }
327                return true;
328            }
329        }
330        return false;
331    }
332
333    /**
334     * Removes the specified connection as a child of this conference.
335     *
336     * @param connection The connection to remove.
337     */
338    public final void removeConnection(Connection connection) {
339        Log.d(this, "removing %s from %s", connection, mChildConnections);
340        if (connection != null && mChildConnections.remove(connection)) {
341            connection.resetConference();
342            for (Listener l : mListeners) {
343                l.onConnectionRemoved(this, connection);
344            }
345        }
346    }
347
348    /**
349     * Sets the connections with which this connection can be conferenced.
350     *
351     * @param conferenceableConnections The set of connections this connection can conference with.
352     */
353    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
354        clearConferenceableList();
355        for (Connection c : conferenceableConnections) {
356            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
357            // small amount of items here.
358            if (!mConferenceableConnections.contains(c)) {
359                c.addConnectionListener(mConnectionDeathListener);
360                mConferenceableConnections.add(c);
361            }
362        }
363        fireOnConferenceableConnectionsChanged();
364    }
365
366    /**
367     * Set the video state for the conference.
368     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
369     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
370     * {@link VideoProfile.VideoState#TX_ENABLED},
371     * {@link VideoProfile.VideoState#RX_ENABLED}.
372     *
373     * @param videoState The new video state.
374     * @hide
375     */
376    public final void setVideoState(Connection c, int videoState) {
377        Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
378                this, c, videoState);
379        for (Listener l : mListeners) {
380            l.onVideoStateChanged(this, videoState);
381        }
382    }
383
384    /**
385     * Sets the video connection provider.
386     *
387     * @param videoProvider The video provider.
388     * @hide
389     */
390    public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
391        Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
392                this, c, videoProvider);
393        for (Listener l : mListeners) {
394            l.onVideoProviderChanged(this, videoProvider);
395        }
396    }
397
398    private final void fireOnConferenceableConnectionsChanged() {
399        for (Listener l : mListeners) {
400            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
401        }
402    }
403
404    /**
405     * Returns the connections with which this connection can be conferenced.
406     */
407    public final List<Connection> getConferenceableConnections() {
408        return mUnmodifiableConferenceableConnections;
409    }
410
411    /**
412     * Tears down the conference object and any of its current connections.
413     */
414    public final void destroy() {
415        Log.d(this, "destroying conference : %s", this);
416        // Tear down the children.
417        for (Connection connection : mChildConnections) {
418            Log.d(this, "removing connection %s", connection);
419            removeConnection(connection);
420        }
421
422        // If not yet disconnected, set the conference call as disconnected first.
423        if (mState != Connection.STATE_DISCONNECTED) {
424            Log.d(this, "setting to disconnected");
425            setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
426        }
427
428        // ...and notify.
429        for (Listener l : mListeners) {
430            l.onDestroyed(this);
431        }
432    }
433
434    /**
435     * Add a listener to be notified of a state change.
436     *
437     * @param listener The new listener.
438     * @return This conference.
439     * @hide
440     */
441    public final Conference addListener(Listener listener) {
442        mListeners.add(listener);
443        return this;
444    }
445
446    /**
447     * Removes the specified listener.
448     *
449     * @param listener The listener to remove.
450     * @return This conference.
451     * @hide
452     */
453    public final Conference removeListener(Listener listener) {
454        mListeners.remove(listener);
455        return this;
456    }
457
458    /**
459     * Retrieves the primary connection associated with the conference.  The primary connection is
460     * the connection from which the conference will retrieve its current state.
461     *
462     * @return The primary connection.
463     */
464    public Connection getPrimaryConnection() {
465        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
466            return null;
467        }
468        return mUnmodifiableChildConnections.get(0);
469    }
470
471    /**
472     * Sets the connect time of the {@code Conference}.
473     *
474     * @param connectTimeMillis The connection time, in milliseconds.
475     */
476    public void setConnectTimeMillis(long connectTimeMillis) {
477        mConnectTimeMillis = connectTimeMillis;
478    }
479
480    /**
481     * Retrieves the connect time of the {@code Conference}, if specified.  A value of
482     * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
483     * of the conference.
484     *
485     * @return The time the {@code Conference} has been connected.
486     */
487    public final long getConnectTimeMillis() {
488        return mConnectTimeMillis;
489    }
490
491    /**
492     * Inform this Conference that the state of its audio output has been changed externally.
493     *
494     * @param state The new audio state.
495     * @hide
496     */
497    final void setAudioState(AudioState state) {
498        Log.d(this, "setAudioState %s", state);
499        mAudioState = state;
500        onAudioStateChanged(state);
501    }
502
503    private void setState(int newState) {
504        if (newState != Connection.STATE_ACTIVE &&
505                newState != Connection.STATE_HOLDING &&
506                newState != Connection.STATE_DISCONNECTED) {
507            Log.w(this, "Unsupported state transition for Conference call.",
508                    Connection.stateToString(newState));
509            return;
510        }
511
512        if (mState != newState) {
513            int oldState = mState;
514            mState = newState;
515            for (Listener l : mListeners) {
516                l.onStateChanged(this, oldState, newState);
517            }
518        }
519    }
520
521    private final void clearConferenceableList() {
522        for (Connection c : mConferenceableConnections) {
523            c.removeConnectionListener(mConnectionDeathListener);
524        }
525        mConferenceableConnections.clear();
526    }
527
528    @Override
529    public String toString() {
530        return String.format(Locale.US,
531                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
532                Connection.stateToString(mState),
533                Call.Details.capabilitiesToString(mConnectionCapabilities),
534                getVideoState(),
535                getVideoProvider(),
536                super.toString());
537    }
538}
539