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