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