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