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