Conference.java revision abfcfdc0444c48dd161e425c8417dab87de1cb69
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 extends 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 CallAudioState mCallAudioState;
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     * @deprecated Use {@link #getCallAudioState()} instead.
177     * @hide
178     */
179    @Deprecated
180    @SystemApi
181    public final AudioState getAudioState() {
182        return new AudioState(mCallAudioState);
183    }
184
185    /**
186     * @return The audio state of the conference, describing how its audio is currently
187     *         being routed by the system. This is {@code null} if this Conference
188     *         does not directly know about its audio state.
189     */
190    public final CallAudioState getCallAudioState() {
191        return mCallAudioState;
192    }
193
194    /**
195     * Returns VideoProvider of the primary call. This can be null.
196     */
197    public VideoProvider getVideoProvider() {
198        return null;
199    }
200
201    /**
202     * Returns video state of the primary call.
203     */
204    public int getVideoState() {
205        return VideoProfile.VideoState.AUDIO_ONLY;
206    }
207
208    /**
209     * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
210     */
211    public void onDisconnect() {}
212
213    /**
214     * Invoked when the specified {@link Connection} should be separated from the conference call.
215     *
216     * @param connection The connection to separate.
217     */
218    public void onSeparate(Connection connection) {}
219
220    /**
221     * Invoked when the specified {@link Connection} should merged with the conference call.
222     *
223     * @param connection The {@code Connection} to merge.
224     */
225    public void onMerge(Connection connection) {}
226
227    /**
228     * Invoked when the conference should be put on hold.
229     */
230    public void onHold() {}
231
232    /**
233     * Invoked when the conference should be moved from hold to active.
234     */
235    public void onUnhold() {}
236
237    /**
238     * Invoked when the child calls should be merged. Only invoked if the conference contains the
239     * capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
240     */
241    public void onMerge() {}
242
243    /**
244     * Invoked when the child calls should be swapped. Only invoked if the conference contains the
245     * capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
246     */
247    public void onSwap() {}
248
249    /**
250     * Notifies this conference of a request to play a DTMF tone.
251     *
252     * @param c A DTMF character.
253     */
254    public void onPlayDtmfTone(char c) {}
255
256    /**
257     * Notifies this conference of a request to stop any currently playing DTMF tones.
258     */
259    public void onStopDtmfTone() {}
260
261    /**
262     * Notifies this conference that the {@link #getAudioState()} property has a new value.
263     *
264     * @param state The new call audio state.
265     * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead.
266     * @hide
267     */
268    @SystemApi
269    @Deprecated
270    public void onAudioStateChanged(AudioState state) {}
271
272    /**
273     * Notifies this conference that the {@link #getCallAudioState()} property has a new value.
274     *
275     * @param state The new call audio state.
276     */
277    public void onCallAudioStateChanged(CallAudioState state) {}
278
279    /**
280     * Notifies this conference that a connection has been added to it.
281     *
282     * @param connection The newly added connection.
283     */
284    public void onConnectionAdded(Connection connection) {}
285
286    /**
287     * Sets state to be on hold.
288     */
289    public final void setOnHold() {
290        setState(Connection.STATE_HOLDING);
291    }
292
293    /**
294     * Sets state to be active.
295     */
296    public final void setActive() {
297        setState(Connection.STATE_ACTIVE);
298    }
299
300    /**
301     * Sets state to disconnected.
302     *
303     * @param disconnectCause The reason for the disconnection, as described by
304     *     {@link android.telecom.DisconnectCause}.
305     */
306    public final void setDisconnected(DisconnectCause disconnectCause) {
307        mDisconnectCause = disconnectCause;;
308        setState(Connection.STATE_DISCONNECTED);
309        for (Listener l : mListeners) {
310            l.onDisconnected(this, mDisconnectCause);
311        }
312    }
313
314    /**
315     * @return The {@link DisconnectCause} for this connection.
316     */
317    public final DisconnectCause getDisconnectCause() {
318        return mDisconnectCause;
319    }
320
321    /**
322     * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
323     * {@link Connection} for valid values.
324     *
325     * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
326     */
327    public final void setConnectionCapabilities(int connectionCapabilities) {
328        if (connectionCapabilities != mConnectionCapabilities) {
329            mConnectionCapabilities = connectionCapabilities;
330
331            for (Listener l : mListeners) {
332                l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
333            }
334        }
335    }
336
337    /**
338     * Adds the specified connection as a child of this conference.
339     *
340     * @param connection The connection to add.
341     * @return True if the connection was successfully added.
342     */
343    public final boolean addConnection(Connection connection) {
344        Log.d(this, "Connection=%s, connection=", connection);
345        if (connection != null && !mChildConnections.contains(connection)) {
346            if (connection.setConference(this)) {
347                mChildConnections.add(connection);
348                onConnectionAdded(connection);
349                for (Listener l : mListeners) {
350                    l.onConnectionAdded(this, connection);
351                }
352                return true;
353            }
354        }
355        return false;
356    }
357
358    /**
359     * Removes the specified connection as a child of this conference.
360     *
361     * @param connection The connection to remove.
362     */
363    public final void removeConnection(Connection connection) {
364        Log.d(this, "removing %s from %s", connection, mChildConnections);
365        if (connection != null && mChildConnections.remove(connection)) {
366            connection.resetConference();
367            for (Listener l : mListeners) {
368                l.onConnectionRemoved(this, connection);
369            }
370        }
371    }
372
373    /**
374     * Sets the connections with which this connection can be conferenced.
375     *
376     * @param conferenceableConnections The set of connections this connection can conference with.
377     */
378    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
379        clearConferenceableList();
380        for (Connection c : conferenceableConnections) {
381            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
382            // small amount of items here.
383            if (!mConferenceableConnections.contains(c)) {
384                c.addConnectionListener(mConnectionDeathListener);
385                mConferenceableConnections.add(c);
386            }
387        }
388        fireOnConferenceableConnectionsChanged();
389    }
390
391    /**
392     * Set the video state for the conference.
393     * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
394     * {@link VideoProfile#STATE_BIDIRECTIONAL},
395     * {@link VideoProfile#STATE_TX_ENABLED},
396     * {@link VideoProfile#STATE_RX_ENABLED}.
397     *
398     * @param videoState The new video state.
399     */
400    public final void setVideoState(Connection c, int videoState) {
401        Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
402                this, c, videoState);
403        for (Listener l : mListeners) {
404            l.onVideoStateChanged(this, videoState);
405        }
406    }
407
408    /**
409     * Sets the video connection provider.
410     *
411     * @param videoProvider The video provider.
412     */
413    public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
414        Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
415                this, c, videoProvider);
416        for (Listener l : mListeners) {
417            l.onVideoProviderChanged(this, videoProvider);
418        }
419    }
420
421    private final void fireOnConferenceableConnectionsChanged() {
422        for (Listener l : mListeners) {
423            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
424        }
425    }
426
427    /**
428     * Returns the connections with which this connection can be conferenced.
429     */
430    public final List<Connection> getConferenceableConnections() {
431        return mUnmodifiableConferenceableConnections;
432    }
433
434    /**
435     * Tears down the conference object and any of its current connections.
436     */
437    public final void destroy() {
438        Log.d(this, "destroying conference : %s", this);
439        // Tear down the children.
440        for (Connection connection : mChildConnections) {
441            Log.d(this, "removing connection %s", connection);
442            removeConnection(connection);
443        }
444
445        // If not yet disconnected, set the conference call as disconnected first.
446        if (mState != Connection.STATE_DISCONNECTED) {
447            Log.d(this, "setting to disconnected");
448            setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
449        }
450
451        // ...and notify.
452        for (Listener l : mListeners) {
453            l.onDestroyed(this);
454        }
455    }
456
457    /**
458     * Add a listener to be notified of a state change.
459     *
460     * @param listener The new listener.
461     * @return This conference.
462     * @hide
463     */
464    public final Conference addListener(Listener listener) {
465        mListeners.add(listener);
466        return this;
467    }
468
469    /**
470     * Removes the specified listener.
471     *
472     * @param listener The listener to remove.
473     * @return This conference.
474     * @hide
475     */
476    public final Conference removeListener(Listener listener) {
477        mListeners.remove(listener);
478        return this;
479    }
480
481    /**
482     * Retrieves the primary connection associated with the conference.  The primary connection is
483     * the connection from which the conference will retrieve its current state.
484     *
485     * @return The primary connection.
486     * @hide
487     */
488    @SystemApi
489    public Connection getPrimaryConnection() {
490        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
491            return null;
492        }
493        return mUnmodifiableChildConnections.get(0);
494    }
495
496    /**
497     * @hide
498     * @deprecated Use {@link #setConnectionTime}.
499     */
500    @Deprecated
501    @SystemApi
502    public final void setConnectTimeMillis(long connectTimeMillis) {
503        setConnectionTime(connectTimeMillis);
504    }
505
506    /**
507     * Sets the connection start time of the {@code Conference}.
508     *
509     * @param connectionTimeMillis The connection time, in milliseconds.
510     */
511    public final void setConnectionTime(long connectionTimeMillis) {
512        mConnectTimeMillis = connectionTimeMillis;
513    }
514
515    /**
516     * @hide
517     * @deprecated Use {@link #getConnectionTime}.
518     */
519    @Deprecated
520    @SystemApi
521    public final long getConnectTimeMillis() {
522        return getConnectionTime();
523    }
524
525    /**
526     * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
527     * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
528     * of the conference.
529     *
530     * @return The time at which the {@code Conference} was connected.
531     */
532    public final long getConnectionTime() {
533        return mConnectTimeMillis;
534    }
535
536    /**
537     * Inform this Conference that the state of its audio output has been changed externally.
538     *
539     * @param state The new audio state.
540     * @hide
541     */
542    final void setCallAudioState(CallAudioState state) {
543        Log.d(this, "setCallAudioState %s", state);
544        mCallAudioState = state;
545        onAudioStateChanged(getAudioState());
546        onCallAudioStateChanged(state);
547    }
548
549    private void setState(int newState) {
550        if (newState != Connection.STATE_ACTIVE &&
551                newState != Connection.STATE_HOLDING &&
552                newState != Connection.STATE_DISCONNECTED) {
553            Log.w(this, "Unsupported state transition for Conference call.",
554                    Connection.stateToString(newState));
555            return;
556        }
557
558        if (mState != newState) {
559            int oldState = mState;
560            mState = newState;
561            for (Listener l : mListeners) {
562                l.onStateChanged(this, oldState, newState);
563            }
564        }
565    }
566
567    private final void clearConferenceableList() {
568        for (Connection c : mConferenceableConnections) {
569            c.removeConnectionListener(mConnectionDeathListener);
570        }
571        mConferenceableConnections.clear();
572    }
573
574    @Override
575    public String toString() {
576        return String.format(Locale.US,
577                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
578                Connection.stateToString(mState),
579                Call.Details.capabilitiesToString(mConnectionCapabilities),
580                getVideoState(),
581                getVideoProvider(),
582                super.toString());
583    }
584
585    /**
586     * Sets the label and icon status to display in the InCall UI.
587     *
588     * @param statusHints The status label and icon to set.
589     */
590    public final void setStatusHints(StatusHints statusHints) {
591        mStatusHints = statusHints;
592        for (Listener l : mListeners) {
593            l.onStatusHintsChanged(this, statusHints);
594        }
595    }
596
597    /**
598     * @return The status hints for this conference.
599     */
600    public final StatusHints getStatusHints() {
601        return mStatusHints;
602    }
603}
604