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 
17 package android.telecom;
18 
19 import com.android.internal.telecom.IConnectionService;
20 
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.RemoteException;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.concurrent.CopyOnWriteArrayList;
32 import 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  */
42 public 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          */
onStateChanged(RemoteConference conference, int oldState, int newState)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          */
onDisconnected(RemoteConference conference, DisconnectCause disconnectCause)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          */
onConnectionAdded(RemoteConference conference, RemoteConnection connection)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          */
onConnectionRemoved(RemoteConference conference, RemoteConnection connection)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          */
onConnectionCapabilitiesChanged( RemoteConference conference, int connectionCapabilities)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          */
onConnectionPropertiesChanged( RemoteConference conference, int connectionProperties)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          */
onConferenceableConnectionsChanged( RemoteConference conference, List<RemoteConnection> conferenceableConnections)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          */
onDestroyed(RemoteConference conference)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          */
onExtrasChanged(RemoteConference conference, @Nullable Bundle extras)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 */
RemoteConference(String id, IConnectionService connectionService)152     RemoteConference(String id, IConnectionService connectionService) {
153         mId = id;
154         mConnectionService = connectionService;
155     }
156 
157     /** @hide */
getId()158     String getId() {
159         return mId;
160     }
161 
162     /** @hide */
setDestroyed()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 */
setState(final int newState)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 */
addConnection(final RemoteConnection connection)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 */
removeConnection(final RemoteConnection connection)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 */
setConnectionCapabilities(final int connectionCapabilities)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 */
setConnectionProperties(final int connectionProperties)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 */
setConferenceableConnections(List<RemoteConnection> conferenceableConnections)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 */
setDisconnected(final DisconnectCause disconnectCause)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 */
putExtras(final Bundle extras)313     void putExtras(final Bundle extras) {
314         if (extras == null) {
315             return;
316         }
317         if (mExtras == null) {
318             mExtras = new Bundle();
319         }
320         mExtras.putAll(extras);
321 
322         notifyExtrasChanged();
323     }
324 
325     /** @hide */
removeExtras(List<String> keys)326     void removeExtras(List<String> keys) {
327         if (mExtras == null || keys == null || keys.isEmpty()) {
328             return;
329         }
330         for (String key : keys) {
331             mExtras.remove(key);
332         }
333 
334         notifyExtrasChanged();
335     }
336 
notifyExtrasChanged()337     private void notifyExtrasChanged() {
338         for (CallbackRecord<Callback> record : mCallbackRecords) {
339             final RemoteConference conference = this;
340             final Callback callback = record.getCallback();
341             record.getHandler().post(new Runnable() {
342                 @Override
343                 public void run() {
344                     callback.onExtrasChanged(conference, mExtras);
345                 }
346             });
347         }
348     }
349 
350     /**
351      * Returns the list of {@link RemoteConnection}s contained in this conference.
352      *
353      * @return A list of child connections.
354      */
getConnections()355     public final List<RemoteConnection> getConnections() {
356         return mUnmodifiableChildConnections;
357     }
358 
359     /**
360      * Gets the state of the conference call. See {@link Connection} for valid values.
361      *
362      * @return A constant representing the state the conference call is currently in.
363      */
getState()364     public final int getState() {
365         return mState;
366     }
367 
368     /**
369      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
370      * {@link Connection} for valid values.
371      *
372      * @return A bitmask of the capabilities of the conference call.
373      */
getConnectionCapabilities()374     public final int getConnectionCapabilities() {
375         return mConnectionCapabilities;
376     }
377 
378     /**
379      * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
380      * {@link Connection} for valid values.
381      *
382      * @return A bitmask of the properties of the conference call.
383      */
getConnectionProperties()384     public final int getConnectionProperties() {
385         return mConnectionProperties;
386     }
387 
388     /**
389      * Obtain the extras associated with this {@code RemoteConnection}.
390      *
391      * @return The extras for this connection.
392      */
getExtras()393     public final Bundle getExtras() {
394         return mExtras;
395     }
396 
397     /**
398      * Disconnects the conference call as well as the child {@link RemoteConnection}s.
399      */
disconnect()400     public void disconnect() {
401         try {
402             mConnectionService.disconnect(mId, null /*Session.Info*/);
403         } catch (RemoteException e) {
404         }
405     }
406 
407     /**
408      * Removes the specified {@link RemoteConnection} from the conference. This causes the
409      * {@link RemoteConnection} to become a standalone connection. This is a no-op if the
410      * {@link RemoteConnection} does not belong to this conference.
411      *
412      * @param connection The remote-connection to remove.
413      */
separate(RemoteConnection connection)414     public void separate(RemoteConnection connection) {
415         if (mChildConnections.contains(connection)) {
416             try {
417                 mConnectionService.splitFromConference(connection.getId(), null /*Session.Info*/);
418             } catch (RemoteException e) {
419             }
420         }
421     }
422 
423     /**
424      * Merges all {@link RemoteConnection}s of this conference into a single call. This should be
425      * invoked only if the conference contains the capability
426      * {@link Connection#CAPABILITY_MERGE_CONFERENCE}, otherwise it is a no-op. The presence of said
427      * capability indicates that the connections of this conference, despite being part of the
428      * same conference object, are yet to have their audio streams merged; this is a common pattern
429      * for CDMA conference calls, but the capability is not used for GSM and SIP conference calls.
430      * Invoking this method will cause the unmerged child connections to merge their audio
431      * streams.
432      */
merge()433     public void merge() {
434         try {
435             mConnectionService.mergeConference(mId, null /*Session.Info*/);
436         } catch (RemoteException e) {
437         }
438     }
439 
440     /**
441      * Swaps the active audio stream between the conference's child {@link RemoteConnection}s.
442      * This should be invoked only if the conference contains the capability
443      * {@link Connection#CAPABILITY_SWAP_CONFERENCE}, otherwise it is a no-op. This is only used by
444      * {@link ConnectionService}s that create conferences for connections that do not yet have
445      * their audio streams merged; this is a common pattern for CDMA conference calls, but the
446      * capability is not used for GSM and SIP conference calls. Invoking this method will change the
447      * active audio stream to a different child connection.
448      */
swap()449     public void swap() {
450         try {
451             mConnectionService.swapConference(mId, null /*Session.Info*/);
452         } catch (RemoteException e) {
453         }
454     }
455 
456     /**
457      * Puts the conference on hold.
458      */
hold()459     public void hold() {
460         try {
461             mConnectionService.hold(mId, null /*Session.Info*/);
462         } catch (RemoteException e) {
463         }
464     }
465 
466     /**
467      * Unholds the conference call.
468      */
unhold()469     public void unhold() {
470         try {
471             mConnectionService.unhold(mId, null /*Session.Info*/);
472         } catch (RemoteException e) {
473         }
474     }
475 
476     /**
477      * Returns the {@link DisconnectCause} for the conference if it is in the state
478      * {@link Connection#STATE_DISCONNECTED}. If the conference is not disconnected, this will
479      * return null.
480      *
481      * @return The disconnect cause.
482      */
getDisconnectCause()483     public DisconnectCause getDisconnectCause() {
484         return mDisconnectCause;
485     }
486 
487     /**
488      * Requests that the conference start playing the specified DTMF tone.
489      *
490      * @param digit The digit for which to play a DTMF tone.
491      */
playDtmfTone(char digit)492     public void playDtmfTone(char digit) {
493         try {
494             mConnectionService.playDtmfTone(mId, digit, null /*Session.Info*/);
495         } catch (RemoteException e) {
496         }
497     }
498 
499     /**
500      * Stops the most recent request to play a DTMF tone.
501      *
502      * @see #playDtmfTone
503      */
stopDtmfTone()504     public void stopDtmfTone() {
505         try {
506             mConnectionService.stopDtmfTone(mId, null /*Session.Info*/);
507         } catch (RemoteException e) {
508         }
509     }
510 
511     /**
512      * Request to change the conference's audio routing to the specified state. The specified state
513      * can include audio routing (Bluetooth, Speaker, etc) and muting state.
514      *
515      * @see android.telecom.AudioState
516      * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead.
517      * @hide
518      */
519     @SystemApi
520     @Deprecated
setAudioState(AudioState state)521     public void setAudioState(AudioState state) {
522         setCallAudioState(new CallAudioState(state));
523     }
524 
525     /**
526      * Request to change the conference's audio routing to the specified state. The specified state
527      * can include audio routing (Bluetooth, Speaker, etc) and muting state.
528      */
setCallAudioState(CallAudioState state)529     public void setCallAudioState(CallAudioState state) {
530         try {
531             mConnectionService.onCallAudioStateChanged(mId, state, null /*Session.Info*/);
532         } catch (RemoteException e) {
533         }
534     }
535 
536 
537     /**
538      * Returns a list of independent connections that can me merged with this conference.
539      *
540      * @return A list of conferenceable connections.
541      */
getConferenceableConnections()542     public List<RemoteConnection> getConferenceableConnections() {
543         return mUnmodifiableConferenceableConnections;
544     }
545 
546     /**
547      * Register a callback through which to receive state updates for this conference.
548      *
549      * @param callback The callback to notify of state changes.
550      */
registerCallback(Callback callback)551     public final void registerCallback(Callback callback) {
552         registerCallback(callback, new Handler());
553     }
554 
555     /**
556      * Registers a callback through which to receive state updates for this conference.
557      * Callbacks will be notified using the specified handler, if provided.
558      *
559      * @param callback The callback to notify of state changes.
560      * @param handler The handler on which to execute the callbacks.
561      */
registerCallback(Callback callback, Handler handler)562     public final void registerCallback(Callback callback, Handler handler) {
563         unregisterCallback(callback);
564         if (callback != null && handler != null) {
565             mCallbackRecords.add(new CallbackRecord(callback, handler));
566         }
567     }
568 
569     /**
570      * Unregisters a previously registered callback.
571      *
572      * @see #registerCallback
573      *
574      * @param callback The callback to unregister.
575      */
unregisterCallback(Callback callback)576     public final void unregisterCallback(Callback callback) {
577         if (callback != null) {
578             for (CallbackRecord<Callback> record : mCallbackRecords) {
579                 if (record.getCallback() == callback) {
580                     mCallbackRecords.remove(record);
581                     break;
582                 }
583             }
584         }
585     }
586 }
587