RemoteConference.java revision 720c664401081ca00e56c7eef12641ae792da530
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 com.android.internal.telecom.IConnectionService;
20
21import android.annotation.Nullable;
22import android.annotation.SystemApi;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.RemoteException;
26
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.List;
30import java.util.Set;
31import java.util.concurrent.CopyOnWriteArrayList;
32import java.util.concurrent.CopyOnWriteArraySet;
33
34/**
35 * A conference provided to a {@link ConnectionService} by another {@code ConnectionService} through
36 * {@link ConnectionService#conferenceRemoteConnections}. Once created, a {@code RemoteConference}
37 * can be used to control the conference call or monitor changes through
38 * {@link RemoteConnection.Callback}.
39 *
40 * @see ConnectionService#onRemoteConferenceAdded
41 */
42public final class RemoteConference {
43
44    /**
45     * Callback base class for {@link RemoteConference}.
46     */
47    public abstract static class Callback {
48        /**
49         * Invoked when the state of this {@code RemoteConferece} has changed. See
50         * {@link #getState()}.
51         *
52         * @param conference The {@code RemoteConference} invoking this method.
53         * @param oldState The previous state of the {@code RemoteConference}.
54         * @param newState The new state of the {@code RemoteConference}.
55         */
56        public void onStateChanged(RemoteConference conference, int oldState, int newState) {}
57
58        /**
59         * Invoked when this {@code RemoteConference} is disconnected.
60         *
61         * @param conference The {@code RemoteConference} invoking this method.
62         * @param disconnectCause The ({@see DisconnectCause}) associated with this failed
63         *     conference.
64         */
65        public void onDisconnected(RemoteConference conference, DisconnectCause disconnectCause) {}
66
67        /**
68         * Invoked when a {@link RemoteConnection} is added to the conference call.
69         *
70         * @param conference The {@code RemoteConference} invoking this method.
71         * @param connection The {@link RemoteConnection} being added.
72         */
73        public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {}
74
75        /**
76         * Invoked when a {@link RemoteConnection} is removed from the conference call.
77         *
78         * @param conference The {@code RemoteConference} invoking this method.
79         * @param connection The {@link RemoteConnection} being removed.
80         */
81        public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {}
82
83        /**
84         * Indicates that the call capabilities of this {@code RemoteConference} have changed.
85         * See {@link #getConnectionCapabilities()}.
86         *
87         * @param conference The {@code RemoteConference} invoking this method.
88         * @param connectionCapabilities The new capabilities of the {@code RemoteConference}.
89         */
90        public void onConnectionCapabilitiesChanged(
91                RemoteConference conference,
92                int connectionCapabilities) {}
93
94        /**
95         * Indicates that the call properties of this {@code RemoteConference} have changed.
96         * See {@link #getConnectionProperties()}.
97         *
98         * @param conference The {@code RemoteConference} invoking this method.
99         * @param connectionProperties The new properties of the {@code RemoteConference}.
100         */
101        public void onConnectionPropertiesChanged(
102                RemoteConference conference,
103                int connectionProperties) {}
104
105
106        /**
107         * Invoked when the set of {@link RemoteConnection}s which can be added to this conference
108         * call have changed.
109         *
110         * @param conference The {@code RemoteConference} invoking this method.
111         * @param conferenceableConnections The list of conferenceable {@link RemoteConnection}s.
112         */
113        public void onConferenceableConnectionsChanged(
114                RemoteConference conference,
115                List<RemoteConnection> conferenceableConnections) {}
116
117        /**
118         * Indicates that this {@code RemoteConference} has been destroyed. No further requests
119         * should be made to the {@code RemoteConference}, and references to it should be cleared.
120         *
121         * @param conference The {@code RemoteConference} invoking this method.
122         */
123        public void onDestroyed(RemoteConference conference) {}
124
125        /**
126         * Handles changes to the {@code RemoteConference} extras.
127         *
128         * @param conference The {@code RemoteConference} invoking this method.
129         * @param extras The extras containing other information associated with the conference.
130         */
131        public void onExtrasChanged(RemoteConference conference, @Nullable Bundle extras) {}
132    }
133
134    private final String mId;
135    private final IConnectionService mConnectionService;
136
137    private final Set<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArraySet<>();
138    private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>();
139    private final List<RemoteConnection> mUnmodifiableChildConnections =
140            Collections.unmodifiableList(mChildConnections);
141    private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>();
142    private final List<RemoteConnection> mUnmodifiableConferenceableConnections =
143            Collections.unmodifiableList(mConferenceableConnections);
144
145    private int mState = Connection.STATE_NEW;
146    private DisconnectCause mDisconnectCause;
147    private int mConnectionCapabilities;
148    private int mConnectionProperties;
149    private Bundle mExtras;
150
151    /** @hide */
152    RemoteConference(String id, IConnectionService connectionService) {
153        mId = id;
154        mConnectionService = connectionService;
155    }
156
157    /** @hide */
158    String getId() {
159        return mId;
160    }
161
162    /** @hide */
163    void setDestroyed() {
164        for (RemoteConnection connection : mChildConnections) {
165            connection.setConference(null);
166        }
167        for (CallbackRecord<Callback> record : mCallbackRecords) {
168            final RemoteConference conference = this;
169            final Callback callback = record.getCallback();
170            record.getHandler().post(new Runnable() {
171                @Override
172                public void run() {
173                    callback.onDestroyed(conference);
174                }
175            });
176        }
177    }
178
179    /** @hide */
180    void setState(final int newState) {
181        if (newState != Connection.STATE_ACTIVE &&
182                newState != Connection.STATE_HOLDING &&
183                newState != Connection.STATE_DISCONNECTED) {
184            Log.w(this, "Unsupported state transition for Conference call.",
185                    Connection.stateToString(newState));
186            return;
187        }
188
189        if (mState != newState) {
190            final int oldState = mState;
191            mState = newState;
192            for (CallbackRecord<Callback> record : mCallbackRecords) {
193                final RemoteConference conference = this;
194                final Callback callback = record.getCallback();
195                record.getHandler().post(new Runnable() {
196                    @Override
197                    public void run() {
198                        callback.onStateChanged(conference, oldState, newState);
199                    }
200                });
201            }
202        }
203    }
204
205    /** @hide */
206    void addConnection(final RemoteConnection connection) {
207        if (!mChildConnections.contains(connection)) {
208            mChildConnections.add(connection);
209            connection.setConference(this);
210            for (CallbackRecord<Callback> record : mCallbackRecords) {
211                final RemoteConference conference = this;
212                final Callback callback = record.getCallback();
213                record.getHandler().post(new Runnable() {
214                    @Override
215                    public void run() {
216                        callback.onConnectionAdded(conference, connection);
217                    }
218                });
219            }
220        }
221    }
222
223    /** @hide */
224    void removeConnection(final RemoteConnection connection) {
225        if (mChildConnections.contains(connection)) {
226            mChildConnections.remove(connection);
227            connection.setConference(null);
228            for (CallbackRecord<Callback> record : mCallbackRecords) {
229                final RemoteConference conference = this;
230                final Callback callback = record.getCallback();
231                record.getHandler().post(new Runnable() {
232                    @Override
233                    public void run() {
234                        callback.onConnectionRemoved(conference, connection);
235                    }
236                });
237            }
238        }
239    }
240
241    /** @hide */
242    void setConnectionCapabilities(final int connectionCapabilities) {
243        if (mConnectionCapabilities != connectionCapabilities) {
244            mConnectionCapabilities = connectionCapabilities;
245            for (CallbackRecord<Callback> record : mCallbackRecords) {
246                final RemoteConference conference = this;
247                final Callback callback = record.getCallback();
248                record.getHandler().post(new Runnable() {
249                    @Override
250                    public void run() {
251                        callback.onConnectionCapabilitiesChanged(
252                                conference, mConnectionCapabilities);
253                    }
254                });
255            }
256        }
257    }
258
259    /** @hide */
260    void setConnectionProperties(final int connectionProperties) {
261        if (mConnectionProperties != connectionProperties) {
262            mConnectionProperties = connectionProperties;
263            for (CallbackRecord<Callback> record : mCallbackRecords) {
264                final RemoteConference conference = this;
265                final Callback callback = record.getCallback();
266                record.getHandler().post(new Runnable() {
267                    @Override
268                    public void run() {
269                        callback.onConnectionPropertiesChanged(
270                                conference, mConnectionProperties);
271                    }
272                });
273            }
274        }
275    }
276
277    /** @hide */
278    void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) {
279        mConferenceableConnections.clear();
280        mConferenceableConnections.addAll(conferenceableConnections);
281        for (CallbackRecord<Callback> record : mCallbackRecords) {
282            final RemoteConference conference = this;
283            final Callback callback = record.getCallback();
284            record.getHandler().post(new Runnable() {
285                @Override
286                public void run() {
287                    callback.onConferenceableConnectionsChanged(
288                            conference, mUnmodifiableConferenceableConnections);
289                }
290            });
291        }
292    }
293
294    /** @hide */
295    void setDisconnected(final DisconnectCause disconnectCause) {
296        if (mState != Connection.STATE_DISCONNECTED) {
297            mDisconnectCause = disconnectCause;
298            setState(Connection.STATE_DISCONNECTED);
299            for (CallbackRecord<Callback> record : mCallbackRecords) {
300                final RemoteConference conference = this;
301                final Callback callback = record.getCallback();
302                record.getHandler().post(new Runnable() {
303                    @Override
304                    public void run() {
305                        callback.onDisconnected(conference, disconnectCause);
306                    }
307                });
308            }
309        }
310    }
311
312    /** @hide */
313    void putExtras(final Bundle extras) {
314        if (mExtras == null) {
315            mExtras = new Bundle();
316        }
317        mExtras.putAll(extras);
318
319        notifyExtrasChanged();
320    }
321
322    /** @hide */
323    void removeExtras(List<String> keys) {
324        if (mExtras == null || keys == null || keys.isEmpty()) {
325            return;
326        }
327        for (String key : keys) {
328            mExtras.remove(key);
329        }
330
331        notifyExtrasChanged();
332    }
333
334    private void notifyExtrasChanged() {
335        for (CallbackRecord<Callback> record : mCallbackRecords) {
336            final RemoteConference conference = this;
337            final Callback callback = record.getCallback();
338            record.getHandler().post(new Runnable() {
339                @Override
340                public void run() {
341                    callback.onExtrasChanged(conference, mExtras);
342                }
343            });
344        }
345    }
346
347    /**
348     * Returns the list of {@link RemoteConnection}s contained in this conference.
349     *
350     * @return A list of child connections.
351     */
352    public final List<RemoteConnection> getConnections() {
353        return mUnmodifiableChildConnections;
354    }
355
356    /**
357     * Gets the state of the conference call. See {@link Connection} for valid values.
358     *
359     * @return A constant representing the state the conference call is currently in.
360     */
361    public final int getState() {
362        return mState;
363    }
364
365    /**
366     * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
367     * {@link Connection} for valid values.
368     *
369     * @return A bitmask of the capabilities of the conference call.
370     */
371    public final int getConnectionCapabilities() {
372        return mConnectionCapabilities;
373    }
374
375    /**
376     * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
377     * {@link Connection} for valid values.
378     *
379     * @return A bitmask of the properties of the conference call.
380     */
381    public final int getConnectionProperties() {
382        return mConnectionProperties;
383    }
384
385    /**
386     * Obtain the extras associated with this {@code RemoteConnection}.
387     *
388     * @return The extras for this connection.
389     */
390    public final Bundle getExtras() {
391        return mExtras;
392    }
393
394    /**
395     * Disconnects the conference call as well as the child {@link RemoteConnection}s.
396     */
397    public void disconnect() {
398        try {
399            mConnectionService.disconnect(mId);
400        } catch (RemoteException e) {
401        }
402    }
403
404    /**
405     * Removes the specified {@link RemoteConnection} from the conference. This causes the
406     * {@link RemoteConnection} to become a standalone connection. This is a no-op if the
407     * {@link RemoteConnection} does not belong to this conference.
408     *
409     * @param connection The remote-connection to remove.
410     */
411    public void separate(RemoteConnection connection) {
412        if (mChildConnections.contains(connection)) {
413            try {
414                mConnectionService.splitFromConference(connection.getId());
415            } catch (RemoteException e) {
416            }
417        }
418    }
419
420    /**
421     * Merges all {@link RemoteConnection}s of this conference into a single call. This should be
422     * invoked only if the conference contains the capability
423     * {@link Connection#CAPABILITY_MERGE_CONFERENCE}, otherwise it is a no-op. The presence of said
424     * capability indicates that the connections of this conference, despite being part of the
425     * same conference object, are yet to have their audio streams merged; this is a common pattern
426     * for CDMA conference calls, but the capability is not used for GSM and SIP conference calls.
427     * Invoking this method will cause the unmerged child connections to merge their audio
428     * streams.
429     */
430    public void merge() {
431        try {
432            mConnectionService.mergeConference(mId);
433        } catch (RemoteException e) {
434        }
435    }
436
437    /**
438     * Swaps the active audio stream between the conference's child {@link RemoteConnection}s.
439     * This should be invoked only if the conference contains the capability
440     * {@link Connection#CAPABILITY_SWAP_CONFERENCE}, otherwise it is a no-op. This is only used by
441     * {@link ConnectionService}s that create conferences for connections that do not yet have
442     * their audio streams merged; this is a common pattern for CDMA conference calls, but the
443     * capability is not used for GSM and SIP conference calls. Invoking this method will change the
444     * active audio stream to a different child connection.
445     */
446    public void swap() {
447        try {
448            mConnectionService.swapConference(mId);
449        } catch (RemoteException e) {
450        }
451    }
452
453    /**
454     * Puts the conference on hold.
455     */
456    public void hold() {
457        try {
458            mConnectionService.hold(mId);
459        } catch (RemoteException e) {
460        }
461    }
462
463    /**
464     * Unholds the conference call.
465     */
466    public void unhold() {
467        try {
468            mConnectionService.unhold(mId);
469        } catch (RemoteException e) {
470        }
471    }
472
473    /**
474     * Returns the {@link DisconnectCause} for the conference if it is in the state
475     * {@link Connection#STATE_DISCONNECTED}. If the conference is not disconnected, this will
476     * return null.
477     *
478     * @return The disconnect cause.
479     */
480    public DisconnectCause getDisconnectCause() {
481        return mDisconnectCause;
482    }
483
484    /**
485     * Requests that the conference start playing the specified DTMF tone.
486     *
487     * @param digit The digit for which to play a DTMF tone.
488     */
489    public void playDtmfTone(char digit) {
490        try {
491            mConnectionService.playDtmfTone(mId, digit);
492        } catch (RemoteException e) {
493        }
494    }
495
496    /**
497     * Stops the most recent request to play a DTMF tone.
498     *
499     * @see #playDtmfTone
500     */
501    public void stopDtmfTone() {
502        try {
503            mConnectionService.stopDtmfTone(mId);
504        } catch (RemoteException e) {
505        }
506    }
507
508    /**
509     * Request to change the conference's audio routing to the specified state. The specified state
510     * can include audio routing (Bluetooth, Speaker, etc) and muting state.
511     *
512     * @see android.telecom.AudioState
513     * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead.
514     * @hide
515     */
516    @SystemApi
517    @Deprecated
518    public void setAudioState(AudioState state) {
519        setCallAudioState(new CallAudioState(state));
520    }
521
522    /**
523     * Request to change the conference's audio routing to the specified state. The specified state
524     * can include audio routing (Bluetooth, Speaker, etc) and muting state.
525     */
526    public void setCallAudioState(CallAudioState state) {
527        try {
528            mConnectionService.onCallAudioStateChanged(mId, state);
529        } catch (RemoteException e) {
530        }
531    }
532
533
534    /**
535     * Returns a list of independent connections that can me merged with this conference.
536     *
537     * @return A list of conferenceable connections.
538     */
539    public List<RemoteConnection> getConferenceableConnections() {
540        return mUnmodifiableConferenceableConnections;
541    }
542
543    /**
544     * Register a callback through which to receive state updates for this conference.
545     *
546     * @param callback The callback to notify of state changes.
547     */
548    public final void registerCallback(Callback callback) {
549        registerCallback(callback, new Handler());
550    }
551
552    /**
553     * Registers a callback through which to receive state updates for this conference.
554     * Callbacks will be notified using the specified handler, if provided.
555     *
556     * @param callback The callback to notify of state changes.
557     * @param handler The handler on which to execute the callbacks.
558     */
559    public final void registerCallback(Callback callback, Handler handler) {
560        unregisterCallback(callback);
561        if (callback != null && handler != null) {
562            mCallbackRecords.add(new CallbackRecord(callback, handler));
563        }
564    }
565
566    /**
567     * Unregisters a previously registered callback.
568     *
569     * @see #registerCallback
570     *
571     * @param callback The callback to unregister.
572     */
573    public final void unregisterCallback(Callback callback) {
574        if (callback != null) {
575            for (CallbackRecord<Callback> record : mCallbackRecords) {
576                if (record.getCallback() == callback) {
577                    mCallbackRecords.remove(record);
578                    break;
579                }
580            }
581        }
582    }
583}
584