[go: nahoru, domu]

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