• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.os.Bundle;
23 import android.os.SystemClock;
24 import android.telecom.Connection.VideoProvider;
25 import android.util.ArraySet;
26 
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Set;
33 import java.util.concurrent.CopyOnWriteArrayList;
34 import java.util.concurrent.CopyOnWriteArraySet;
35 
36 /**
37  * Represents a conference call which can contain any number of {@link Connection} objects.
38  */
39 public abstract class Conference extends Conferenceable {
40 
41     /**
42      * Used to indicate that the conference connection time is not specified.  If not specified,
43      * Telecom will set the connect time.
44      */
45     public static final long CONNECT_TIME_NOT_SPECIFIED = 0;
46 
47     /** @hide */
48     public abstract static class Listener {
onStateChanged(Conference conference, int oldState, int newState)49         public void onStateChanged(Conference conference, int oldState, int newState) {}
onDisconnected(Conference conference, DisconnectCause disconnectCause)50         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
onConnectionAdded(Conference conference, Connection connection)51         public void onConnectionAdded(Conference conference, Connection connection) {}
onConnectionRemoved(Conference conference, Connection connection)52         public void onConnectionRemoved(Conference conference, Connection connection) {}
onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections)53         public void onConferenceableConnectionsChanged(
54                 Conference conference, List<Connection> conferenceableConnections) {}
onDestroyed(Conference conference)55         public void onDestroyed(Conference conference) {}
onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities)56         public void onConnectionCapabilitiesChanged(
57                 Conference conference, int connectionCapabilities) {}
onConnectionPropertiesChanged( Conference conference, int connectionProperties)58         public void onConnectionPropertiesChanged(
59                 Conference conference, int connectionProperties) {}
onVideoStateChanged(Conference c, int videoState)60         public void onVideoStateChanged(Conference c, int videoState) { }
onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider)61         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
onStatusHintsChanged(Conference conference, StatusHints statusHints)62         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
onExtrasChanged(Conference c, Bundle extras)63         public void onExtrasChanged(Conference c, Bundle extras) {}
onExtrasRemoved(Conference c, List<String> keys)64         public void onExtrasRemoved(Conference c, List<String> keys) {}
65     }
66 
67     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
68     private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
69     private final List<Connection> mUnmodifiableChildConnections =
70             Collections.unmodifiableList(mChildConnections);
71     private final List<Connection> mConferenceableConnections = new ArrayList<>();
72     private final List<Connection> mUnmodifiableConferenceableConnections =
73             Collections.unmodifiableList(mConferenceableConnections);
74 
75     private String mTelecomCallId;
76     private PhoneAccountHandle mPhoneAccount;
77     private CallAudioState mCallAudioState;
78     private int mState = Connection.STATE_NEW;
79     private DisconnectCause mDisconnectCause;
80     private int mConnectionCapabilities;
81     private int mConnectionProperties;
82     private String mDisconnectMessage;
83     private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
84     private long mConnectionStartElapsedRealTime = CONNECT_TIME_NOT_SPECIFIED;
85     private StatusHints mStatusHints;
86     private Bundle mExtras;
87     private Set<String> mPreviousExtraKeys;
88     private final Object mExtrasLock = new Object();
89 
90     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
91         @Override
92         public void onDestroyed(Connection c) {
93             if (mConferenceableConnections.remove(c)) {
94                 fireOnConferenceableConnectionsChanged();
95             }
96         }
97     };
98 
99     /**
100      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
101      *
102      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
103      */
Conference(PhoneAccountHandle phoneAccount)104     public Conference(PhoneAccountHandle phoneAccount) {
105         mPhoneAccount = phoneAccount;
106     }
107 
108     /**
109      * Returns the telecom internal call ID associated with this conference.
110      *
111      * @return The telecom call ID.
112      * @hide
113      */
getTelecomCallId()114     public final String getTelecomCallId() {
115         return mTelecomCallId;
116     }
117 
118     /**
119      * Sets the telecom internal call ID associated with this conference.
120      *
121      * @param telecomCallId The telecom call ID.
122      * @hide
123      */
setTelecomCallId(String telecomCallId)124     public final void setTelecomCallId(String telecomCallId) {
125         mTelecomCallId = telecomCallId;
126     }
127 
128     /**
129      * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
130      *
131      * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
132      */
getPhoneAccountHandle()133     public final PhoneAccountHandle getPhoneAccountHandle() {
134         return mPhoneAccount;
135     }
136 
137     /**
138      * Returns the list of connections currently associated with the conference call.
139      *
140      * @return A list of {@code Connection} objects which represent the children of the conference.
141      */
getConnections()142     public final List<Connection> getConnections() {
143         return mUnmodifiableChildConnections;
144     }
145 
146     /**
147      * Gets the state of the conference call. See {@link Connection} for valid values.
148      *
149      * @return A constant representing the state the conference call is currently in.
150      */
getState()151     public final int getState() {
152         return mState;
153     }
154 
155     /**
156      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
157      * {@link Connection} for valid values.
158      *
159      * @return A bitmask of the capabilities of the conference call.
160      */
getConnectionCapabilities()161     public final int getConnectionCapabilities() {
162         return mConnectionCapabilities;
163     }
164 
165     /**
166      * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
167      * {@link Connection} for valid values.
168      *
169      * @return A bitmask of the properties of the conference call.
170      */
getConnectionProperties()171     public final int getConnectionProperties() {
172         return mConnectionProperties;
173     }
174 
175     /**
176      * Whether the given capabilities support the specified capability.
177      *
178      * @param capabilities A capability bit field.
179      * @param capability The capability to check capabilities for.
180      * @return Whether the specified capability is supported.
181      * @hide
182      */
can(int capabilities, int capability)183     public static boolean can(int capabilities, int capability) {
184         return (capabilities & capability) != 0;
185     }
186 
187     /**
188      * Whether the capabilities of this {@code Connection} supports the specified capability.
189      *
190      * @param capability The capability to check capabilities for.
191      * @return Whether the specified capability is supported.
192      * @hide
193      */
can(int capability)194     public boolean can(int capability) {
195         return can(mConnectionCapabilities, capability);
196     }
197 
198     /**
199      * Removes the specified capability from the set of capabilities of this {@code Conference}.
200      *
201      * @param capability The capability to remove from the set.
202      * @hide
203      */
removeCapability(int capability)204     public void removeCapability(int capability) {
205         int newCapabilities = mConnectionCapabilities;
206         newCapabilities &= ~capability;
207 
208         setConnectionCapabilities(newCapabilities);
209     }
210 
211     /**
212      * Adds the specified capability to the set of capabilities of this {@code Conference}.
213      *
214      * @param capability The capability to add to the set.
215      * @hide
216      */
addCapability(int capability)217     public void addCapability(int capability) {
218         int newCapabilities = mConnectionCapabilities;
219         newCapabilities |= capability;
220 
221         setConnectionCapabilities(newCapabilities);
222     }
223 
224     /**
225      * @return The audio state of the conference, describing how its audio is currently
226      *         being routed by the system. This is {@code null} if this Conference
227      *         does not directly know about its audio state.
228      * @deprecated Use {@link #getCallAudioState()} instead.
229      * @hide
230      */
231     @Deprecated
232     @SystemApi
getAudioState()233     public final AudioState getAudioState() {
234         return new AudioState(mCallAudioState);
235     }
236 
237     /**
238      * @return The audio state of the conference, describing how its audio is currently
239      *         being routed by the system. This is {@code null} if this Conference
240      *         does not directly know about its audio state.
241      */
getCallAudioState()242     public final CallAudioState getCallAudioState() {
243         return mCallAudioState;
244     }
245 
246     /**
247      * Returns VideoProvider of the primary call. This can be null.
248      */
getVideoProvider()249     public VideoProvider getVideoProvider() {
250         return null;
251     }
252 
253     /**
254      * Returns video state of the primary call.
255      */
getVideoState()256     public int getVideoState() {
257         return VideoProfile.STATE_AUDIO_ONLY;
258     }
259 
260     /**
261      * Notifies the {@link Conference} when the Conference and all it's {@link Connection}s should
262      * be disconnected.
263      */
onDisconnect()264     public void onDisconnect() {}
265 
266     /**
267      * Notifies the {@link Conference} when the specified {@link Connection} should be separated
268      * from the conference call.
269      *
270      * @param connection The connection to separate.
271      */
onSeparate(Connection connection)272     public void onSeparate(Connection connection) {}
273 
274     /**
275      * Notifies the {@link Conference} when the specified {@link Connection} should merged with the
276      * conference call.
277      *
278      * @param connection The {@code Connection} to merge.
279      */
onMerge(Connection connection)280     public void onMerge(Connection connection) {}
281 
282     /**
283      * Notifies the {@link Conference} when it should be put on hold.
284      */
onHold()285     public void onHold() {}
286 
287     /**
288      * Notifies the {@link Conference} when it should be moved from a held to active state.
289      */
onUnhold()290     public void onUnhold() {}
291 
292     /**
293      * Notifies the {@link Conference} when the child calls should be merged.  Only invoked if the
294      * conference contains the capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
295      */
onMerge()296     public void onMerge() {}
297 
298     /**
299      * Notifies the {@link Conference} when the child calls should be swapped. Only invoked if the
300      * conference contains the capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
301      */
onSwap()302     public void onSwap() {}
303 
304     /**
305      * Notifies the {@link Conference} of a request to play a DTMF tone.
306      *
307      * @param c A DTMF character.
308      */
onPlayDtmfTone(char c)309     public void onPlayDtmfTone(char c) {}
310 
311     /**
312      * Notifies the {@link Conference} of a request to stop any currently playing DTMF tones.
313      */
onStopDtmfTone()314     public void onStopDtmfTone() {}
315 
316     /**
317      * Notifies the {@link Conference} that the {@link #getAudioState()} property has a new value.
318      *
319      * @param state The new call audio state.
320      * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead.
321      * @hide
322      */
323     @SystemApi
324     @Deprecated
onAudioStateChanged(AudioState state)325     public void onAudioStateChanged(AudioState state) {}
326 
327     /**
328      * Notifies the {@link Conference} that the {@link #getCallAudioState()} property has a new
329      * value.
330      *
331      * @param state The new call audio state.
332      */
onCallAudioStateChanged(CallAudioState state)333     public void onCallAudioStateChanged(CallAudioState state) {}
334 
335     /**
336      * Notifies the {@link Conference} that a {@link Connection} has been added to it.
337      *
338      * @param connection The newly added connection.
339      */
onConnectionAdded(Connection connection)340     public void onConnectionAdded(Connection connection) {}
341 
342     /**
343      * Sets state to be on hold.
344      */
setOnHold()345     public final void setOnHold() {
346         setState(Connection.STATE_HOLDING);
347     }
348 
349     /**
350      * Sets state to be dialing.
351      */
setDialing()352     public final void setDialing() {
353         setState(Connection.STATE_DIALING);
354     }
355 
356     /**
357      * Sets state to be active.
358      */
setActive()359     public final void setActive() {
360         setState(Connection.STATE_ACTIVE);
361     }
362 
363     /**
364      * Sets state to disconnected.
365      *
366      * @param disconnectCause The reason for the disconnection, as described by
367      *     {@link android.telecom.DisconnectCause}.
368      */
setDisconnected(DisconnectCause disconnectCause)369     public final void setDisconnected(DisconnectCause disconnectCause) {
370         mDisconnectCause = disconnectCause;;
371         setState(Connection.STATE_DISCONNECTED);
372         for (Listener l : mListeners) {
373             l.onDisconnected(this, mDisconnectCause);
374         }
375     }
376 
377     /**
378      * @return The {@link DisconnectCause} for this connection.
379      */
getDisconnectCause()380     public final DisconnectCause getDisconnectCause() {
381         return mDisconnectCause;
382     }
383 
384     /**
385      * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
386      * {@link Connection} for valid values.
387      *
388      * @param connectionCapabilities A bitmask of the {@code Capabilities} of the conference call.
389      */
setConnectionCapabilities(int connectionCapabilities)390     public final void setConnectionCapabilities(int connectionCapabilities) {
391         if (connectionCapabilities != mConnectionCapabilities) {
392             mConnectionCapabilities = connectionCapabilities;
393 
394             for (Listener l : mListeners) {
395                 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
396             }
397         }
398     }
399 
400     /**
401      * Sets the properties of a conference. See {@code PROPERTY_*} constants of class
402      * {@link Connection} for valid values.
403      *
404      * @param connectionProperties A bitmask of the {@code Properties} of the conference call.
405      */
setConnectionProperties(int connectionProperties)406     public final void setConnectionProperties(int connectionProperties) {
407         if (connectionProperties != mConnectionProperties) {
408             mConnectionProperties = connectionProperties;
409 
410             for (Listener l : mListeners) {
411                 l.onConnectionPropertiesChanged(this, mConnectionProperties);
412             }
413         }
414     }
415 
416     /**
417      * Adds the specified connection as a child of this conference.
418      *
419      * @param connection The connection to add.
420      * @return True if the connection was successfully added.
421      */
addConnection(Connection connection)422     public final boolean addConnection(Connection connection) {
423         Log.d(this, "Connection=%s, connection=", connection);
424         if (connection != null && !mChildConnections.contains(connection)) {
425             if (connection.setConference(this)) {
426                 mChildConnections.add(connection);
427                 onConnectionAdded(connection);
428                 for (Listener l : mListeners) {
429                     l.onConnectionAdded(this, connection);
430                 }
431                 return true;
432             }
433         }
434         return false;
435     }
436 
437     /**
438      * Removes the specified connection as a child of this conference.
439      *
440      * @param connection The connection to remove.
441      */
removeConnection(Connection connection)442     public final void removeConnection(Connection connection) {
443         Log.d(this, "removing %s from %s", connection, mChildConnections);
444         if (connection != null && mChildConnections.remove(connection)) {
445             connection.resetConference();
446             for (Listener l : mListeners) {
447                 l.onConnectionRemoved(this, connection);
448             }
449         }
450     }
451 
452     /**
453      * Sets the connections with which this connection can be conferenced.
454      *
455      * @param conferenceableConnections The set of connections this connection can conference with.
456      */
setConferenceableConnections(List<Connection> conferenceableConnections)457     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
458         clearConferenceableList();
459         for (Connection c : conferenceableConnections) {
460             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
461             // small amount of items here.
462             if (!mConferenceableConnections.contains(c)) {
463                 c.addConnectionListener(mConnectionDeathListener);
464                 mConferenceableConnections.add(c);
465             }
466         }
467         fireOnConferenceableConnectionsChanged();
468     }
469 
470     /**
471      * Set the video state for the conference.
472      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
473      * {@link VideoProfile#STATE_BIDIRECTIONAL},
474      * {@link VideoProfile#STATE_TX_ENABLED},
475      * {@link VideoProfile#STATE_RX_ENABLED}.
476      *
477      * @param videoState The new video state.
478      */
setVideoState(Connection c, int videoState)479     public final void setVideoState(Connection c, int videoState) {
480         Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
481                 this, c, videoState);
482         for (Listener l : mListeners) {
483             l.onVideoStateChanged(this, videoState);
484         }
485     }
486 
487     /**
488      * Sets the video connection provider.
489      *
490      * @param videoProvider The video provider.
491      */
setVideoProvider(Connection c, Connection.VideoProvider videoProvider)492     public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
493         Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
494                 this, c, videoProvider);
495         for (Listener l : mListeners) {
496             l.onVideoProviderChanged(this, videoProvider);
497         }
498     }
499 
fireOnConferenceableConnectionsChanged()500     private final void fireOnConferenceableConnectionsChanged() {
501         for (Listener l : mListeners) {
502             l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
503         }
504     }
505 
506     /**
507      * Returns the connections with which this connection can be conferenced.
508      */
getConferenceableConnections()509     public final List<Connection> getConferenceableConnections() {
510         return mUnmodifiableConferenceableConnections;
511     }
512 
513     /**
514      * Tears down the conference object and any of its current connections.
515      */
destroy()516     public final void destroy() {
517         Log.d(this, "destroying conference : %s", this);
518         // Tear down the children.
519         for (Connection connection : mChildConnections) {
520             Log.d(this, "removing connection %s", connection);
521             removeConnection(connection);
522         }
523 
524         // If not yet disconnected, set the conference call as disconnected first.
525         if (mState != Connection.STATE_DISCONNECTED) {
526             Log.d(this, "setting to disconnected");
527             setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
528         }
529 
530         // ...and notify.
531         for (Listener l : mListeners) {
532             l.onDestroyed(this);
533         }
534     }
535 
536     /**
537      * Add a listener to be notified of a state change.
538      *
539      * @param listener The new listener.
540      * @return This conference.
541      * @hide
542      */
addListener(Listener listener)543     public final Conference addListener(Listener listener) {
544         mListeners.add(listener);
545         return this;
546     }
547 
548     /**
549      * Removes the specified listener.
550      *
551      * @param listener The listener to remove.
552      * @return This conference.
553      * @hide
554      */
removeListener(Listener listener)555     public final Conference removeListener(Listener listener) {
556         mListeners.remove(listener);
557         return this;
558     }
559 
560     /**
561      * Retrieves the primary connection associated with the conference.  The primary connection is
562      * the connection from which the conference will retrieve its current state.
563      *
564      * @return The primary connection.
565      * @hide
566      */
567     @SystemApi
getPrimaryConnection()568     public Connection getPrimaryConnection() {
569         if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
570             return null;
571         }
572         return mUnmodifiableChildConnections.get(0);
573     }
574 
575     /**
576      * @hide
577      * @deprecated Use {@link #setConnectionTime}.
578      */
579     @Deprecated
580     @SystemApi
setConnectTimeMillis(long connectTimeMillis)581     public final void setConnectTimeMillis(long connectTimeMillis) {
582         setConnectionTime(connectTimeMillis);
583     }
584 
585     /**
586      * Sets the connection start time of the {@code Conference}.  This is used in the call log to
587      * indicate the date and time when the conference took place.
588      * <p>
589      * Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
590      * <p>
591      * When setting the connection time, you should always set the connection elapsed time via
592      * {@link #setConnectionStartElapsedRealTime(long)} to ensure the duration is reflected.
593      *
594      * @param connectionTimeMillis The connection time, in milliseconds, as returned by
595      *                             {@link System#currentTimeMillis()}.
596      */
setConnectionTime(long connectionTimeMillis)597     public final void setConnectionTime(long connectionTimeMillis) {
598         mConnectTimeMillis = connectionTimeMillis;
599     }
600 
601     /**
602      * Sets the start time of the {@link Conference} which is the basis for the determining the
603      * duration of the {@link Conference}.
604      * <p>
605      * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
606      * zone changes do not impact the conference duration.
607      * <p>
608      * When setting this, you should also set the connection time via
609      * {@link #setConnectionTime(long)}.
610      *
611      * @param connectionStartElapsedRealTime The connection time, as measured by
612      * {@link SystemClock#elapsedRealtime()}.
613      */
setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime)614     public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
615         mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
616     }
617 
618     /**
619      * @hide
620      * @deprecated Use {@link #getConnectionTime}.
621      */
622     @Deprecated
623     @SystemApi
getConnectTimeMillis()624     public final long getConnectTimeMillis() {
625         return getConnectionTime();
626     }
627 
628     /**
629      * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
630      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
631      * of the conference.
632      *
633      * @return The time at which the {@code Conference} was connected.
634      */
getConnectionTime()635     public final long getConnectionTime() {
636         return mConnectTimeMillis;
637     }
638 
639     /**
640      * Retrieves the connection start time of the {@link Conference}, if specified.  A value of
641      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
642      * of the conference.
643      *
644      * This is based on the value of {@link SystemClock#elapsedRealtime()} to ensure that it is not
645      * impacted by wall clock changes (user initiated, network initiated, time zone change, etc).
646      *
647      * @return The elapsed time at which the {@link Conference} was connected.
648      * @hide
649      */
getConnectionStartElapsedRealTime()650     public final long getConnectionStartElapsedRealTime() {
651         return mConnectionStartElapsedRealTime;
652     }
653 
654     /**
655      * Inform this Conference that the state of its audio output has been changed externally.
656      *
657      * @param state The new audio state.
658      * @hide
659      */
setCallAudioState(CallAudioState state)660     final void setCallAudioState(CallAudioState state) {
661         Log.d(this, "setCallAudioState %s", state);
662         mCallAudioState = state;
663         onAudioStateChanged(getAudioState());
664         onCallAudioStateChanged(state);
665     }
666 
setState(int newState)667     private void setState(int newState) {
668         if (newState != Connection.STATE_ACTIVE &&
669                 newState != Connection.STATE_HOLDING &&
670                 newState != Connection.STATE_DISCONNECTED) {
671             Log.w(this, "Unsupported state transition for Conference call.",
672                     Connection.stateToString(newState));
673             return;
674         }
675 
676         if (mState != newState) {
677             int oldState = mState;
678             mState = newState;
679             for (Listener l : mListeners) {
680                 l.onStateChanged(this, oldState, newState);
681             }
682         }
683     }
684 
clearConferenceableList()685     private final void clearConferenceableList() {
686         for (Connection c : mConferenceableConnections) {
687             c.removeConnectionListener(mConnectionDeathListener);
688         }
689         mConferenceableConnections.clear();
690     }
691 
692     @Override
toString()693     public String toString() {
694         return String.format(Locale.US,
695                 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
696                 Connection.stateToString(mState),
697                 Call.Details.capabilitiesToString(mConnectionCapabilities),
698                 getVideoState(),
699                 getVideoProvider(),
700                 super.toString());
701     }
702 
703     /**
704      * Sets the label and icon status to display in the InCall UI.
705      *
706      * @param statusHints The status label and icon to set.
707      */
setStatusHints(StatusHints statusHints)708     public final void setStatusHints(StatusHints statusHints) {
709         mStatusHints = statusHints;
710         for (Listener l : mListeners) {
711             l.onStatusHintsChanged(this, statusHints);
712         }
713     }
714 
715     /**
716      * @return The status hints for this conference.
717      */
getStatusHints()718     public final StatusHints getStatusHints() {
719         return mStatusHints;
720     }
721 
722     /**
723      * Replaces all the extras associated with this {@code Conference}.
724      * <p>
725      * New or existing keys are replaced in the {@code Conference} extras.  Keys which are no longer
726      * in the new extras, but were present the last time {@code setExtras} was called are removed.
727      * <p>
728      * Alternatively you may use the {@link #putExtras(Bundle)}, and
729      * {@link #removeExtras(String...)} methods to modify the extras.
730      * <p>
731      * No assumptions should be made as to how an In-Call UI or service will handle these extras.
732      * Keys should be fully qualified (e.g., com.example.extras.MY_EXTRA) to avoid conflicts.
733      *
734      * @param extras The extras associated with this {@code Conference}.
735      */
setExtras(@ullable Bundle extras)736     public final void setExtras(@Nullable Bundle extras) {
737         // Keeping putExtras and removeExtras in the same lock so that this operation happens as a
738         // block instead of letting other threads put/remove while this method is running.
739         synchronized (mExtrasLock) {
740             // Add/replace any new or changed extras values.
741             putExtras(extras);
742             // If we have used "setExtras" in the past, compare the key set from the last invocation
743             // to the current one and remove any keys that went away.
744             if (mPreviousExtraKeys != null) {
745                 List<String> toRemove = new ArrayList<String>();
746                 for (String oldKey : mPreviousExtraKeys) {
747                     if (extras == null || !extras.containsKey(oldKey)) {
748                         toRemove.add(oldKey);
749                     }
750                 }
751 
752                 if (!toRemove.isEmpty()) {
753                     removeExtras(toRemove);
754                 }
755             }
756 
757             // Track the keys the last time set called setExtras.  This way, the next time setExtras
758             // is called we can see if the caller has removed any extras values.
759             if (mPreviousExtraKeys == null) {
760                 mPreviousExtraKeys = new ArraySet<String>();
761             }
762             mPreviousExtraKeys.clear();
763             if (extras != null) {
764                 mPreviousExtraKeys.addAll(extras.keySet());
765             }
766         }
767     }
768 
769     /**
770      * Adds some extras to this {@link Conference}.  Existing keys are replaced and new ones are
771      * added.
772      * <p>
773      * No assumptions should be made as to how an In-Call UI or service will handle these extras.
774      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
775      *
776      * @param extras The extras to add.
777      */
putExtras(@onNull Bundle extras)778     public final void putExtras(@NonNull Bundle extras) {
779         if (extras == null) {
780             return;
781         }
782 
783         // Creating a Bundle clone so we don't have to synchronize on mExtrasLock while calling
784         // onExtrasChanged.
785         Bundle listenersBundle;
786         synchronized (mExtrasLock) {
787             if (mExtras == null) {
788                 mExtras = new Bundle();
789             }
790             mExtras.putAll(extras);
791             listenersBundle = new Bundle(mExtras);
792         }
793 
794         for (Listener l : mListeners) {
795             l.onExtrasChanged(this, new Bundle(listenersBundle));
796         }
797     }
798 
799     /**
800      * Adds a boolean extra to this {@link Conference}.
801      *
802      * @param key The extra key.
803      * @param value The value.
804      * @hide
805      */
putExtra(String key, boolean value)806     public final void putExtra(String key, boolean value) {
807         Bundle newExtras = new Bundle();
808         newExtras.putBoolean(key, value);
809         putExtras(newExtras);
810     }
811 
812     /**
813      * Adds an integer extra to this {@link Conference}.
814      *
815      * @param key The extra key.
816      * @param value The value.
817      * @hide
818      */
putExtra(String key, int value)819     public final void putExtra(String key, int value) {
820         Bundle newExtras = new Bundle();
821         newExtras.putInt(key, value);
822         putExtras(newExtras);
823     }
824 
825     /**
826      * Adds a string extra to this {@link Conference}.
827      *
828      * @param key The extra key.
829      * @param value The value.
830      * @hide
831      */
putExtra(String key, String value)832     public final void putExtra(String key, String value) {
833         Bundle newExtras = new Bundle();
834         newExtras.putString(key, value);
835         putExtras(newExtras);
836     }
837 
838     /**
839      * Removes extras from this {@link Conference}.
840      *
841      * @param keys The keys of the extras to remove.
842      */
removeExtras(List<String> keys)843     public final void removeExtras(List<String> keys) {
844         if (keys == null || keys.isEmpty()) {
845             return;
846         }
847 
848         synchronized (mExtrasLock) {
849             if (mExtras != null) {
850                 for (String key : keys) {
851                     mExtras.remove(key);
852                 }
853             }
854         }
855 
856         List<String> unmodifiableKeys = Collections.unmodifiableList(keys);
857         for (Listener l : mListeners) {
858             l.onExtrasRemoved(this, unmodifiableKeys);
859         }
860     }
861 
862     /**
863      * Removes extras from this {@link Conference}.
864      *
865      * @param keys The keys of the extras to remove.
866      */
removeExtras(String .... keys)867     public final void removeExtras(String ... keys) {
868         removeExtras(Arrays.asList(keys));
869     }
870 
871     /**
872      * Returns the extras associated with this conference.
873      * <p>
874      * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}.
875      * <p>
876      * Telecom or an {@link InCallService} can also update the extras via
877      * {@link android.telecom.Call#putExtras(Bundle)}, and
878      * {@link Call#removeExtras(List)}.
879      * <p>
880      * The conference is notified of changes to the extras made by Telecom or an
881      * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
882      *
883      * @return The extras associated with this connection.
884      */
getExtras()885     public final Bundle getExtras() {
886         return mExtras;
887     }
888 
889     /**
890      * Notifies this {@link Conference} of a change to the extras made outside the
891      * {@link ConnectionService}.
892      * <p>
893      * These extras changes can originate from Telecom itself, or from an {@link InCallService} via
894      * {@link android.telecom.Call#putExtras(Bundle)}, and
895      * {@link Call#removeExtras(List)}.
896      *
897      * @param extras The new extras bundle.
898      */
onExtrasChanged(Bundle extras)899     public void onExtrasChanged(Bundle extras) {}
900 
901     /**
902      * Handles a change to extras received from Telecom.
903      *
904      * @param extras The new extras.
905      * @hide
906      */
handleExtrasChanged(Bundle extras)907     final void handleExtrasChanged(Bundle extras) {
908         Bundle b = null;
909         synchronized (mExtrasLock) {
910             mExtras = extras;
911             if (mExtras != null) {
912                 b = new Bundle(mExtras);
913             }
914         }
915         onExtrasChanged(b);
916     }
917 }
918