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