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