Conference.java revision d46595a673eba0df624d9d92b6f981e3483e0d20
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 dialing.
299     */
300    public final void setDialing() {
301        setState(Connection.STATE_DIALING);
302    }
303
304    /**
305     * Sets state to be active.
306     */
307    public final void setActive() {
308        setState(Connection.STATE_ACTIVE);
309    }
310
311    /**
312     * Sets state to disconnected.
313     *
314     * @param disconnectCause The reason for the disconnection, as described by
315     *     {@link android.telecom.DisconnectCause}.
316     */
317    public final void setDisconnected(DisconnectCause disconnectCause) {
318        mDisconnectCause = disconnectCause;;
319        setState(Connection.STATE_DISCONNECTED);
320        for (Listener l : mListeners) {
321            l.onDisconnected(this, mDisconnectCause);
322        }
323    }
324
325    /**
326     * @return The {@link DisconnectCause} for this connection.
327     */
328    public final DisconnectCause getDisconnectCause() {
329        return mDisconnectCause;
330    }
331
332    /**
333     * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
334     * {@link Connection} for valid values.
335     *
336     * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
337     */
338    public final void setConnectionCapabilities(int connectionCapabilities) {
339        if (connectionCapabilities != mConnectionCapabilities) {
340            mConnectionCapabilities = connectionCapabilities;
341
342            for (Listener l : mListeners) {
343                l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
344            }
345        }
346    }
347
348    /**
349     * Adds the specified connection as a child of this conference.
350     *
351     * @param connection The connection to add.
352     * @return True if the connection was successfully added.
353     */
354    public final boolean addConnection(Connection connection) {
355        Log.d(this, "Connection=%s, connection=", connection);
356        if (connection != null && !mChildConnections.contains(connection)) {
357            if (connection.setConference(this)) {
358                mChildConnections.add(connection);
359                onConnectionAdded(connection);
360                for (Listener l : mListeners) {
361                    l.onConnectionAdded(this, connection);
362                }
363                return true;
364            }
365        }
366        return false;
367    }
368
369    /**
370     * Removes the specified connection as a child of this conference.
371     *
372     * @param connection The connection to remove.
373     */
374    public final void removeConnection(Connection connection) {
375        Log.d(this, "removing %s from %s", connection, mChildConnections);
376        if (connection != null && mChildConnections.remove(connection)) {
377            connection.resetConference();
378            for (Listener l : mListeners) {
379                l.onConnectionRemoved(this, connection);
380            }
381        }
382    }
383
384    /**
385     * Sets the connections with which this connection can be conferenced.
386     *
387     * @param conferenceableConnections The set of connections this connection can conference with.
388     */
389    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
390        clearConferenceableList();
391        for (Connection c : conferenceableConnections) {
392            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
393            // small amount of items here.
394            if (!mConferenceableConnections.contains(c)) {
395                c.addConnectionListener(mConnectionDeathListener);
396                mConferenceableConnections.add(c);
397            }
398        }
399        fireOnConferenceableConnectionsChanged();
400    }
401
402    /**
403     * Set the video state for the conference.
404     * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
405     * {@link VideoProfile#STATE_BIDIRECTIONAL},
406     * {@link VideoProfile#STATE_TX_ENABLED},
407     * {@link VideoProfile#STATE_RX_ENABLED}.
408     *
409     * @param videoState The new video state.
410     */
411    public final void setVideoState(Connection c, int videoState) {
412        Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
413                this, c, videoState);
414        for (Listener l : mListeners) {
415            l.onVideoStateChanged(this, videoState);
416        }
417    }
418
419    /**
420     * Sets the video connection provider.
421     *
422     * @param videoProvider The video provider.
423     */
424    public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
425        Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
426                this, c, videoProvider);
427        for (Listener l : mListeners) {
428            l.onVideoProviderChanged(this, videoProvider);
429        }
430    }
431
432    private final void fireOnConferenceableConnectionsChanged() {
433        for (Listener l : mListeners) {
434            l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
435        }
436    }
437
438    /**
439     * Returns the connections with which this connection can be conferenced.
440     */
441    public final List<Connection> getConferenceableConnections() {
442        return mUnmodifiableConferenceableConnections;
443    }
444
445    /**
446     * Tears down the conference object and any of its current connections.
447     */
448    public final void destroy() {
449        Log.d(this, "destroying conference : %s", this);
450        // Tear down the children.
451        for (Connection connection : mChildConnections) {
452            Log.d(this, "removing connection %s", connection);
453            removeConnection(connection);
454        }
455
456        // If not yet disconnected, set the conference call as disconnected first.
457        if (mState != Connection.STATE_DISCONNECTED) {
458            Log.d(this, "setting to disconnected");
459            setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
460        }
461
462        // ...and notify.
463        for (Listener l : mListeners) {
464            l.onDestroyed(this);
465        }
466    }
467
468    /**
469     * Add a listener to be notified of a state change.
470     *
471     * @param listener The new listener.
472     * @return This conference.
473     * @hide
474     */
475    public final Conference addListener(Listener listener) {
476        mListeners.add(listener);
477        return this;
478    }
479
480    /**
481     * Removes the specified listener.
482     *
483     * @param listener The listener to remove.
484     * @return This conference.
485     * @hide
486     */
487    public final Conference removeListener(Listener listener) {
488        mListeners.remove(listener);
489        return this;
490    }
491
492    /**
493     * Retrieves the primary connection associated with the conference.  The primary connection is
494     * the connection from which the conference will retrieve its current state.
495     *
496     * @return The primary connection.
497     * @hide
498     */
499    @SystemApi
500    public Connection getPrimaryConnection() {
501        if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
502            return null;
503        }
504        return mUnmodifiableChildConnections.get(0);
505    }
506
507    /**
508     * @hide
509     * @deprecated Use {@link #setConnectionTime}.
510     */
511    @Deprecated
512    @SystemApi
513    public final void setConnectTimeMillis(long connectTimeMillis) {
514        setConnectionTime(connectTimeMillis);
515    }
516
517    /**
518     * Sets the connection start time of the {@code Conference}.
519     *
520     * @param connectionTimeMillis The connection time, in milliseconds.
521     */
522    public final void setConnectionTime(long connectionTimeMillis) {
523        mConnectTimeMillis = connectionTimeMillis;
524    }
525
526    /**
527     * @hide
528     * @deprecated Use {@link #getConnectionTime}.
529     */
530    @Deprecated
531    @SystemApi
532    public final long getConnectTimeMillis() {
533        return getConnectionTime();
534    }
535
536    /**
537     * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
538     * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
539     * of the conference.
540     *
541     * @return The time at which the {@code Conference} was connected.
542     */
543    public final long getConnectionTime() {
544        return mConnectTimeMillis;
545    }
546
547    /**
548     * Inform this Conference that the state of its audio output has been changed externally.
549     *
550     * @param state The new audio state.
551     * @hide
552     */
553    final void setCallAudioState(CallAudioState state) {
554        Log.d(this, "setCallAudioState %s", state);
555        mCallAudioState = state;
556        onAudioStateChanged(getAudioState());
557        onCallAudioStateChanged(state);
558    }
559
560    private void setState(int newState) {
561        if (newState != Connection.STATE_ACTIVE &&
562                newState != Connection.STATE_HOLDING &&
563                newState != Connection.STATE_DISCONNECTED) {
564            Log.w(this, "Unsupported state transition for Conference call.",
565                    Connection.stateToString(newState));
566            return;
567        }
568
569        if (mState != newState) {
570            int oldState = mState;
571            mState = newState;
572            for (Listener l : mListeners) {
573                l.onStateChanged(this, oldState, newState);
574            }
575        }
576    }
577
578    private final void clearConferenceableList() {
579        for (Connection c : mConferenceableConnections) {
580            c.removeConnectionListener(mConnectionDeathListener);
581        }
582        mConferenceableConnections.clear();
583    }
584
585    @Override
586    public String toString() {
587        return String.format(Locale.US,
588                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
589                Connection.stateToString(mState),
590                Call.Details.capabilitiesToString(mConnectionCapabilities),
591                getVideoState(),
592                getVideoProvider(),
593                super.toString());
594    }
595
596    /**
597     * Sets the label and icon status to display in the InCall UI.
598     *
599     * @param statusHints The status label and icon to set.
600     */
601    public final void setStatusHints(StatusHints statusHints) {
602        mStatusHints = statusHints;
603        for (Listener l : mListeners) {
604            l.onStatusHintsChanged(this, statusHints);
605        }
606    }
607
608    /**
609     * @return The status hints for this conference.
610     */
611    public final StatusHints getStatusHints() {
612        return mStatusHints;
613    }
614
615    /**
616     * Set some extras that can be associated with this {@code Conference}. No assumptions should
617     * be made as to how an In-Call UI or service will handle these extras.
618     * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
619     *
620     * @param extras The extras associated with this {@code Connection}.
621     */
622    public final void setExtras(@Nullable Bundle extras) {
623        mExtras = extras;
624        for (Listener l : mListeners) {
625            l.onExtrasChanged(this, extras);
626        }
627    }
628
629    /**
630     * @return The extras associated with this conference.
631     */
632    public final Bundle getExtras() {
633        return mExtras;
634    }
635}
636