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 android.annotation.Nullable;
20 import android.annotation.SystemApi;
21 import android.os.Bundle;
22 import android.telecom.Connection.VideoProvider;
23 
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Set;
29 import java.util.concurrent.CopyOnWriteArrayList;
30 import java.util.concurrent.CopyOnWriteArraySet;
31 
32 /**
33  * Represents a conference call which can contain any number of {@link Connection} objects.
34  */
35 public abstract class Conference extends Conferenceable {
36 
37     /**
38      * Used to indicate that the conference connection time is not specified.  If not specified,
39      * Telecom will set the connect time.
40      */
41     public static final long CONNECT_TIME_NOT_SPECIFIED = 0;
42 
43     /** @hide */
44     public abstract static class Listener {
onStateChanged(Conference conference, int oldState, int newState)45         public void onStateChanged(Conference conference, int oldState, int newState) {}
onDisconnected(Conference conference, DisconnectCause disconnectCause)46         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
onConnectionAdded(Conference conference, Connection connection)47         public void onConnectionAdded(Conference conference, Connection connection) {}
onConnectionRemoved(Conference conference, Connection connection)48         public void onConnectionRemoved(Conference conference, Connection connection) {}
onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections)49         public void onConferenceableConnectionsChanged(
50                 Conference conference, List<Connection> conferenceableConnections) {}
onDestroyed(Conference conference)51         public void onDestroyed(Conference conference) {}
onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities)52         public void onConnectionCapabilitiesChanged(
53                 Conference conference, int connectionCapabilities) {}
onVideoStateChanged(Conference c, int videoState)54         public void onVideoStateChanged(Conference c, int videoState) { }
onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider)55         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
onStatusHintsChanged(Conference conference, StatusHints statusHints)56         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
onExtrasChanged(Conference conference, Bundle extras)57         public void onExtrasChanged(Conference conference, Bundle extras) {}
58     }
59 
60     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
61     private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
62     private final List<Connection> mUnmodifiableChildConnections =
63             Collections.unmodifiableList(mChildConnections);
64     private final List<Connection> mConferenceableConnections = new ArrayList<>();
65     private final List<Connection> mUnmodifiableConferenceableConnections =
66             Collections.unmodifiableList(mConferenceableConnections);
67 
68     private PhoneAccountHandle mPhoneAccount;
69     private CallAudioState mCallAudioState;
70     private int mState = Connection.STATE_NEW;
71     private DisconnectCause mDisconnectCause;
72     private int mConnectionCapabilities;
73     private String mDisconnectMessage;
74     private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
75     private StatusHints mStatusHints;
76     private Bundle mExtras;
77 
78     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
79         @Override
80         public void onDestroyed(Connection c) {
81             if (mConferenceableConnections.remove(c)) {
82                 fireOnConferenceableConnectionsChanged();
83             }
84         }
85     };
86 
87     /**
88      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
89      *
90      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
91      */
Conference(PhoneAccountHandle phoneAccount)92     public Conference(PhoneAccountHandle phoneAccount) {
93         mPhoneAccount = phoneAccount;
94     }
95 
96     /**
97      * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
98      *
99      * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
100      */
getPhoneAccountHandle()101     public final PhoneAccountHandle getPhoneAccountHandle() {
102         return mPhoneAccount;
103     }
104 
105     /**
106      * Returns the list of connections currently associated with the conference call.
107      *
108      * @return A list of {@code Connection} objects which represent the children of the conference.
109      */
getConnections()110     public final List<Connection> getConnections() {
111         return mUnmodifiableChildConnections;
112     }
113 
114     /**
115      * Gets the state of the conference call. See {@link Connection} for valid values.
116      *
117      * @return A constant representing the state the conference call is currently in.
118      */
getState()119     public final int getState() {
120         return mState;
121     }
122 
123     /**
124      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
125      * {@link Connection} for valid values.
126      *
127      * @return A bitmask of the capabilities of the conference call.
128      */
getConnectionCapabilities()129     public final int getConnectionCapabilities() {
130         return mConnectionCapabilities;
131     }
132 
133     /**
134      * Whether the given capabilities support the specified capability.
135      *
136      * @param capabilities A capability bit field.
137      * @param capability The capability to check capabilities for.
138      * @return Whether the specified capability is supported.
139      * @hide
140      */
can(int capabilities, int capability)141     public static boolean can(int capabilities, int capability) {
142         return (capabilities & capability) != 0;
143     }
144 
145     /**
146      * Whether the capabilities of this {@code Connection} supports the specified capability.
147      *
148      * @param capability The capability to check capabilities for.
149      * @return Whether the specified capability is supported.
150      * @hide
151      */
can(int capability)152     public boolean can(int capability) {
153         return can(mConnectionCapabilities, capability);
154     }
155 
156     /**
157      * Removes the specified capability from the set of capabilities of this {@code Conference}.
158      *
159      * @param capability The capability to remove from the set.
160      * @hide
161      */
removeCapability(int capability)162     public void removeCapability(int capability) {
163         mConnectionCapabilities &= ~capability;
164     }
165 
166     /**
167      * Adds the specified capability to the set of capabilities of this {@code Conference}.
168      *
169      * @param capability The capability to add to the set.
170      * @hide
171      */
addCapability(int capability)172     public void addCapability(int capability) {
173         mConnectionCapabilities |= capability;
174     }
175 
176     /**
177      * @return The audio state of the conference, describing how its audio is currently
178      *         being routed by the system. This is {@code null} if this Conference
179      *         does not directly know about its audio state.
180      * @deprecated Use {@link #getCallAudioState()} instead.
181      * @hide
182      */
183     @Deprecated
184     @SystemApi
getAudioState()185     public final AudioState getAudioState() {
186         return new AudioState(mCallAudioState);
187     }
188 
189     /**
190      * @return The audio state of the conference, describing how its audio is currently
191      *         being routed by the system. This is {@code null} if this Conference
192      *         does not directly know about its audio state.
193      */
getCallAudioState()194     public final CallAudioState getCallAudioState() {
195         return mCallAudioState;
196     }
197 
198     /**
199      * Returns VideoProvider of the primary call. This can be null.
200      */
getVideoProvider()201     public VideoProvider getVideoProvider() {
202         return null;
203     }
204 
205     /**
206      * Returns video state of the primary call.
207      */
getVideoState()208     public int getVideoState() {
209         return VideoProfile.STATE_AUDIO_ONLY;
210     }
211 
212     /**
213      * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
214      */
onDisconnect()215     public void onDisconnect() {}
216 
217     /**
218      * Invoked when the specified {@link Connection} should be separated from the conference call.
219      *
220      * @param connection The connection to separate.
221      */
onSeparate(Connection connection)222     public void onSeparate(Connection connection) {}
223 
224     /**
225      * Invoked when the specified {@link Connection} should merged with the conference call.
226      *
227      * @param connection The {@code Connection} to merge.
228      */
onMerge(Connection connection)229     public void onMerge(Connection connection) {}
230 
231     /**
232      * Invoked when the conference should be put on hold.
233      */
onHold()234     public void onHold() {}
235 
236     /**
237      * Invoked when the conference should be moved from hold to active.
238      */
onUnhold()239     public void onUnhold() {}
240 
241     /**
242      * Invoked when the child calls should be merged. Only invoked if the conference contains the
243      * capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
244      */
onMerge()245     public void onMerge() {}
246 
247     /**
248      * Invoked when the child calls should be swapped. Only invoked if the conference contains the
249      * capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
250      */
onSwap()251     public void onSwap() {}
252 
253     /**
254      * Notifies this conference of a request to play a DTMF tone.
255      *
256      * @param c A DTMF character.
257      */
onPlayDtmfTone(char c)258     public void onPlayDtmfTone(char c) {}
259 
260     /**
261      * Notifies this conference of a request to stop any currently playing DTMF tones.
262      */
onStopDtmfTone()263     public void onStopDtmfTone() {}
264 
265     /**
266      * Notifies this conference that the {@link #getAudioState()} property has a new value.
267      *
268      * @param state The new call audio state.
269      * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead.
270      * @hide
271      */
272     @SystemApi
273     @Deprecated
onAudioStateChanged(AudioState state)274     public void onAudioStateChanged(AudioState state) {}
275 
276     /**
277      * Notifies this conference that the {@link #getCallAudioState()} property has a new value.
278      *
279      * @param state The new call audio state.
280      */
onCallAudioStateChanged(CallAudioState state)281     public void onCallAudioStateChanged(CallAudioState state) {}
282 
283     /**
284      * Notifies this conference that a connection has been added to it.
285      *
286      * @param connection The newly added connection.
287      */
onConnectionAdded(Connection connection)288     public void onConnectionAdded(Connection connection) {}
289 
290     /**
291      * Sets state to be on hold.
292      */
setOnHold()293     public final void setOnHold() {
294         setState(Connection.STATE_HOLDING);
295     }
296 
297     /**
298      * Sets state to be dialing.
299      */
setDialing()300     public final void setDialing() {
301         setState(Connection.STATE_DIALING);
302     }
303 
304     /**
305      * Sets state to be active.
306      */
setActive()307     public final void setActive() {
308         setState(Connection.STATE_ACTIVE);
309     }
310 
311     /**
312      * Sets state to disconnected.
313      *
314      * @param disconnectCause The reason for the disconnection, as described by
315      *     {@link android.telecom.DisconnectCause}.
316      */
setDisconnected(DisconnectCause disconnectCause)317     public final void setDisconnected(DisconnectCause disconnectCause) {
318         mDisconnectCause = disconnectCause;;
319         setState(Connection.STATE_DISCONNECTED);
320         for (Listener l : mListeners) {
321             l.onDisconnected(this, mDisconnectCause);
322         }
323     }
324 
325     /**
326      * @return The {@link DisconnectCause} for this connection.
327      */
getDisconnectCause()328     public final DisconnectCause getDisconnectCause() {
329         return mDisconnectCause;
330     }
331 
332     /**
333      * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
334      * {@link Connection} for valid values.
335      *
336      * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
337      */
setConnectionCapabilities(int connectionCapabilities)338     public final void setConnectionCapabilities(int connectionCapabilities) {
339         if (connectionCapabilities != mConnectionCapabilities) {
340             mConnectionCapabilities = connectionCapabilities;
341 
342             for (Listener l : mListeners) {
343                 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
344             }
345         }
346     }
347 
348     /**
349      * Adds the specified connection as a child of this conference.
350      *
351      * @param connection The connection to add.
352      * @return True if the connection was successfully added.
353      */
addConnection(Connection connection)354     public final boolean addConnection(Connection connection) {
355         Log.d(this, "Connection=%s, connection=", connection);
356         if (connection != null && !mChildConnections.contains(connection)) {
357             if (connection.setConference(this)) {
358                 mChildConnections.add(connection);
359                 onConnectionAdded(connection);
360                 for (Listener l : mListeners) {
361                     l.onConnectionAdded(this, connection);
362                 }
363                 return true;
364             }
365         }
366         return false;
367     }
368 
369     /**
370      * Removes the specified connection as a child of this conference.
371      *
372      * @param connection The connection to remove.
373      */
removeConnection(Connection connection)374     public final void removeConnection(Connection connection) {
375         Log.d(this, "removing %s from %s", connection, mChildConnections);
376         if (connection != null && mChildConnections.remove(connection)) {
377             connection.resetConference();
378             for (Listener l : mListeners) {
379                 l.onConnectionRemoved(this, connection);
380             }
381         }
382     }
383 
384     /**
385      * Sets the connections with which this connection can be conferenced.
386      *
387      * @param conferenceableConnections The set of connections this connection can conference with.
388      */
setConferenceableConnections(List<Connection> conferenceableConnections)389     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
390         clearConferenceableList();
391         for (Connection c : conferenceableConnections) {
392             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
393             // small amount of items here.
394             if (!mConferenceableConnections.contains(c)) {
395                 c.addConnectionListener(mConnectionDeathListener);
396                 mConferenceableConnections.add(c);
397             }
398         }
399         fireOnConferenceableConnectionsChanged();
400     }
401 
402     /**
403      * Set the video state for the conference.
404      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
405      * {@link VideoProfile#STATE_BIDIRECTIONAL},
406      * {@link VideoProfile#STATE_TX_ENABLED},
407      * {@link VideoProfile#STATE_RX_ENABLED}.
408      *
409      * @param videoState The new video state.
410      */
setVideoState(Connection c, int videoState)411     public final void setVideoState(Connection c, int videoState) {
412         Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
413                 this, c, videoState);
414         for (Listener l : mListeners) {
415             l.onVideoStateChanged(this, videoState);
416         }
417     }
418 
419     /**
420      * Sets the video connection provider.
421      *
422      * @param videoProvider The video provider.
423      */
setVideoProvider(Connection c, Connection.VideoProvider videoProvider)424     public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
425         Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
426                 this, c, videoProvider);
427         for (Listener l : mListeners) {
428             l.onVideoProviderChanged(this, videoProvider);
429         }
430     }
431 
fireOnConferenceableConnectionsChanged()432     private final void fireOnConferenceableConnectionsChanged() {
433         for (Listener l : mListeners) {
434             l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
435         }
436     }
437 
438     /**
439      * Returns the connections with which this connection can be conferenced.
440      */
getConferenceableConnections()441     public final List<Connection> getConferenceableConnections() {
442         return mUnmodifiableConferenceableConnections;
443     }
444 
445     /**
446      * Tears down the conference object and any of its current connections.
447      */
destroy()448     public final void destroy() {
449         Log.d(this, "destroying conference : %s", this);
450         // Tear down the children.
451         for (Connection connection : mChildConnections) {
452             Log.d(this, "removing connection %s", connection);
453             removeConnection(connection);
454         }
455 
456         // If not yet disconnected, set the conference call as disconnected first.
457         if (mState != Connection.STATE_DISCONNECTED) {
458             Log.d(this, "setting to disconnected");
459             setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
460         }
461 
462         // ...and notify.
463         for (Listener l : mListeners) {
464             l.onDestroyed(this);
465         }
466     }
467 
468     /**
469      * Add a listener to be notified of a state change.
470      *
471      * @param listener The new listener.
472      * @return This conference.
473      * @hide
474      */
addListener(Listener listener)475     public final Conference addListener(Listener listener) {
476         mListeners.add(listener);
477         return this;
478     }
479 
480     /**
481      * Removes the specified listener.
482      *
483      * @param listener The listener to remove.
484      * @return This conference.
485      * @hide
486      */
removeListener(Listener listener)487     public final Conference removeListener(Listener listener) {
488         mListeners.remove(listener);
489         return this;
490     }
491 
492     /**
493      * Retrieves the primary connection associated with the conference.  The primary connection is
494      * the connection from which the conference will retrieve its current state.
495      *
496      * @return The primary connection.
497      * @hide
498      */
499     @SystemApi
getPrimaryConnection()500     public Connection getPrimaryConnection() {
501         if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
502             return null;
503         }
504         return mUnmodifiableChildConnections.get(0);
505     }
506 
507     /**
508      * @hide
509      * @deprecated Use {@link #setConnectionTime}.
510      */
511     @Deprecated
512     @SystemApi
setConnectTimeMillis(long connectTimeMillis)513     public final void setConnectTimeMillis(long connectTimeMillis) {
514         setConnectionTime(connectTimeMillis);
515     }
516 
517     /**
518      * Sets the connection start time of the {@code Conference}.
519      *
520      * @param connectionTimeMillis The connection time, in milliseconds.
521      */
setConnectionTime(long connectionTimeMillis)522     public final void setConnectionTime(long connectionTimeMillis) {
523         mConnectTimeMillis = connectionTimeMillis;
524     }
525 
526     /**
527      * @hide
528      * @deprecated Use {@link #getConnectionTime}.
529      */
530     @Deprecated
531     @SystemApi
getConnectTimeMillis()532     public final long getConnectTimeMillis() {
533         return getConnectionTime();
534     }
535 
536     /**
537      * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
538      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
539      * of the conference.
540      *
541      * @return The time at which the {@code Conference} was connected.
542      */
getConnectionTime()543     public final long getConnectionTime() {
544         return mConnectTimeMillis;
545     }
546 
547     /**
548      * Inform this Conference that the state of its audio output has been changed externally.
549      *
550      * @param state The new audio state.
551      * @hide
552      */
setCallAudioState(CallAudioState state)553     final void setCallAudioState(CallAudioState state) {
554         Log.d(this, "setCallAudioState %s", state);
555         mCallAudioState = state;
556         onAudioStateChanged(getAudioState());
557         onCallAudioStateChanged(state);
558     }
559 
setState(int newState)560     private void setState(int newState) {
561         if (newState != Connection.STATE_ACTIVE &&
562                 newState != Connection.STATE_HOLDING &&
563                 newState != Connection.STATE_DISCONNECTED) {
564             Log.w(this, "Unsupported state transition for Conference call.",
565                     Connection.stateToString(newState));
566             return;
567         }
568 
569         if (mState != newState) {
570             int oldState = mState;
571             mState = newState;
572             for (Listener l : mListeners) {
573                 l.onStateChanged(this, oldState, newState);
574             }
575         }
576     }
577 
clearConferenceableList()578     private final void clearConferenceableList() {
579         for (Connection c : mConferenceableConnections) {
580             c.removeConnectionListener(mConnectionDeathListener);
581         }
582         mConferenceableConnections.clear();
583     }
584 
585     @Override
toString()586     public String toString() {
587         return String.format(Locale.US,
588                 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
589                 Connection.stateToString(mState),
590                 Call.Details.capabilitiesToString(mConnectionCapabilities),
591                 getVideoState(),
592                 getVideoProvider(),
593                 super.toString());
594     }
595 
596     /**
597      * Sets the label and icon status to display in the InCall UI.
598      *
599      * @param statusHints The status label and icon to set.
600      */
setStatusHints(StatusHints statusHints)601     public final void setStatusHints(StatusHints statusHints) {
602         mStatusHints = statusHints;
603         for (Listener l : mListeners) {
604             l.onStatusHintsChanged(this, statusHints);
605         }
606     }
607 
608     /**
609      * @return The status hints for this conference.
610      */
getStatusHints()611     public final StatusHints getStatusHints() {
612         return mStatusHints;
613     }
614 
615     /**
616      * Set some extras that can be associated with this {@code Conference}. No assumptions should
617      * be made as to how an In-Call UI or service will handle these extras.
618      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
619      *
620      * @param extras The extras associated with this {@code Connection}.
621      */
setExtras(@ullable Bundle extras)622     public final void setExtras(@Nullable Bundle extras) {
623         mExtras = extras;
624         for (Listener l : mListeners) {
625             l.onExtrasChanged(this, extras);
626         }
627     }
628 
629     /**
630      * @return The extras associated with this conference.
631      */
getExtras()632     public final Bundle getExtras() {
633         return mExtras;
634     }
635 }
636