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 static android.Manifest.permission.MODIFY_PHONE_STATE;
20 
21 import android.annotation.ElapsedRealtimeLong;
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemApi;
27 import android.annotation.TestApi;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.SystemClock;
31 import android.telecom.Connection.VideoProvider;
32 import android.util.ArraySet;
33 
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Set;
40 import java.util.concurrent.CopyOnWriteArrayList;
41 import java.util.concurrent.CopyOnWriteArraySet;
42 
43 /**
44  * Represents a conference call which can contain any number of {@link Connection} objects.
45  */
46 public abstract class Conference extends Conferenceable {
47 
48     /**
49      * Used to indicate that the conference connection time is not specified.  If not specified,
50      * Telecom will set the connect time.
51      */
52     public static final long CONNECT_TIME_NOT_SPECIFIED = 0;
53 
54     /** @hide */
55     abstract static class Listener {
onStateChanged(Conference conference, int oldState, int newState)56         public void onStateChanged(Conference conference, int oldState, int newState) {}
onDisconnected(Conference conference, DisconnectCause disconnectCause)57         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
onConnectionAdded(Conference conference, Connection connection)58         public void onConnectionAdded(Conference conference, Connection connection) {}
onConnectionRemoved(Conference conference, Connection connection)59         public void onConnectionRemoved(Conference conference, Connection connection) {}
onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections)60         public void onConferenceableConnectionsChanged(
61                 Conference conference, List<Connection> conferenceableConnections) {}
onDestroyed(Conference conference)62         public void onDestroyed(Conference conference) {}
onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities)63         public void onConnectionCapabilitiesChanged(
64                 Conference conference, int connectionCapabilities) {}
onConnectionPropertiesChanged( Conference conference, int connectionProperties)65         public void onConnectionPropertiesChanged(
66                 Conference conference, int connectionProperties) {}
onVideoStateChanged(Conference c, int videoState)67         public void onVideoStateChanged(Conference c, int videoState) { }
onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider)68         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
onStatusHintsChanged(Conference conference, StatusHints statusHints)69         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
onExtrasChanged(Conference c, Bundle extras)70         public void onExtrasChanged(Conference c, Bundle extras) {}
onExtrasRemoved(Conference c, List<String> keys)71         public void onExtrasRemoved(Conference c, List<String> keys) {}
onConferenceStateChanged(Conference c, boolean isConference)72         public void onConferenceStateChanged(Conference c, boolean isConference) {}
onAddressChanged(Conference c, Uri newAddress, int presentation)73         public void onAddressChanged(Conference c, Uri newAddress, int presentation) {}
onConnectionEvent(Conference c, String event, Bundle extras)74         public void onConnectionEvent(Conference c, String event, Bundle extras) {}
onCallerDisplayNameChanged( Conference c, String callerDisplayName, int presentation)75         public void onCallerDisplayNameChanged(
76                 Conference c, String callerDisplayName, int presentation) {}
onCallDirectionChanged(Conference c, int callDirection)77         public void onCallDirectionChanged(Conference c, int callDirection) {}
onRingbackRequested(Conference c, boolean ringback)78         public void onRingbackRequested(Conference c, boolean ringback) {}
79     }
80 
81     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
82     private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
83     private final List<Connection> mUnmodifiableChildConnections =
84             Collections.unmodifiableList(mChildConnections);
85     private final List<Connection> mConferenceableConnections = new ArrayList<>();
86     private final List<Connection> mUnmodifiableConferenceableConnections =
87             Collections.unmodifiableList(mConferenceableConnections);
88 
89     private String mTelecomCallId;
90     private PhoneAccountHandle mPhoneAccount;
91     private CallAudioState mCallAudioState;
92     private int mState = Connection.STATE_NEW;
93     private DisconnectCause mDisconnectCause;
94     private int mConnectionCapabilities;
95     private int mConnectionProperties;
96     private String mDisconnectMessage;
97     private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
98     private long mConnectionStartElapsedRealTime = CONNECT_TIME_NOT_SPECIFIED;
99     private StatusHints mStatusHints;
100     private Bundle mExtras;
101     private Set<String> mPreviousExtraKeys;
102     private final Object mExtrasLock = new Object();
103     private Uri mAddress;
104     private int mAddressPresentation;
105     private String mCallerDisplayName;
106     private int mCallerDisplayNamePresentation;
107     private int mCallDirection;
108     private boolean mRingbackRequested = false;
109     private boolean mIsMultiparty = true;
110 
111     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
112         @Override
113         public void onDestroyed(Connection c) {
114             if (mConferenceableConnections.remove(c)) {
115                 fireOnConferenceableConnectionsChanged();
116             }
117         }
118     };
119 
120     /**
121      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
122      *
123      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
124      */
Conference(PhoneAccountHandle phoneAccount)125     public Conference(PhoneAccountHandle phoneAccount) {
126         mPhoneAccount = phoneAccount;
127     }
128 
129     /**
130      * Returns the telecom internal call ID associated with this conference.
131      * <p>
132      * Note: This is ONLY used for debugging purposes so that the Telephony stack can better
133      * associate logs in Telephony with those in Telecom.
134      * The ID returned should not be used for any other purpose.
135      *
136      * @return The telecom call ID.
137      * @hide
138      */
139     @SystemApi
140     @TestApi
getTelecomCallId()141     public final @NonNull String getTelecomCallId() {
142         return mTelecomCallId;
143     }
144 
145     /**
146      * Sets the telecom internal call ID associated with this conference.
147      *
148      * @param telecomCallId The telecom call ID.
149      * @hide
150      */
setTelecomCallId(String telecomCallId)151     public final void setTelecomCallId(String telecomCallId) {
152         mTelecomCallId = telecomCallId;
153     }
154 
155     /**
156      * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
157      *
158      * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
159      */
getPhoneAccountHandle()160     public final PhoneAccountHandle getPhoneAccountHandle() {
161         return mPhoneAccount;
162     }
163 
164     /**
165      * Returns the list of connections currently associated with the conference call.
166      *
167      * @return A list of {@code Connection} objects which represent the children of the conference.
168      */
getConnections()169     public final List<Connection> getConnections() {
170         return mUnmodifiableChildConnections;
171     }
172 
173     /**
174      * Gets the state of the conference call. See {@link Connection} for valid values.
175      *
176      * @return A constant representing the state the conference call is currently in.
177      */
getState()178     public final int getState() {
179         return mState;
180     }
181 
182     /**
183      * Returns whether this conference is requesting that the system play a ringback tone
184      * on its behalf.
185      * @hide
186      */
isRingbackRequested()187     public final boolean isRingbackRequested() {
188         return mRingbackRequested;
189     }
190 
191     /**
192      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
193      * {@link Connection} for valid values.
194      *
195      * @return A bitmask of the capabilities of the conference call.
196      */
getConnectionCapabilities()197     public final int getConnectionCapabilities() {
198         return mConnectionCapabilities;
199     }
200 
201     /**
202      * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
203      * {@link Connection} for valid values.
204      *
205      * @return A bitmask of the properties of the conference call.
206      */
getConnectionProperties()207     public final int getConnectionProperties() {
208         return mConnectionProperties;
209     }
210 
211     /**
212      * @return The audio state of the conference, describing how its audio is currently
213      *         being routed by the system. This is {@code null} if this Conference
214      *         does not directly know about its audio state.
215      * @deprecated Use {@link #getCallAudioState()} instead.
216      * @hide
217      */
218     @Deprecated
219     @SystemApi
getAudioState()220     public final AudioState getAudioState() {
221         return new AudioState(mCallAudioState);
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      */
getCallAudioState()229     public final CallAudioState getCallAudioState() {
230         return mCallAudioState;
231     }
232 
233     /**
234      * Returns VideoProvider of the primary call. This can be null.
235      */
getVideoProvider()236     public VideoProvider getVideoProvider() {
237         return null;
238     }
239 
240     /**
241      * Returns video state of the primary call.
242      */
getVideoState()243     public int getVideoState() {
244         return VideoProfile.STATE_AUDIO_ONLY;
245     }
246 
247     /**
248      * Notifies the {@link Conference} when the Conference and all it's {@link Connection}s should
249      * be disconnected.
250      */
onDisconnect()251     public void onDisconnect() {}
252 
253     /**
254      * Notifies the {@link Conference} when the specified {@link Connection} should be separated
255      * from the conference call.
256      *
257      * @param connection The connection to separate.
258      */
onSeparate(Connection connection)259     public void onSeparate(Connection connection) {}
260 
261     /**
262      * Notifies the {@link Conference} when the specified {@link Connection} should merged with the
263      * conference call.
264      *
265      * @param connection The {@code Connection} to merge.
266      */
onMerge(Connection connection)267     public void onMerge(Connection connection) {}
268 
269     /**
270      * Notifies the {@link Conference} when it should be put on hold.
271      */
onHold()272     public void onHold() {}
273 
274     /**
275      * Notifies the {@link Conference} when it should be moved from a held to active state.
276      */
onUnhold()277     public void onUnhold() {}
278 
279     /**
280      * Notifies the {@link Conference} when the child calls should be merged.  Only invoked if the
281      * conference contains the capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
282      */
onMerge()283     public void onMerge() {}
284 
285     /**
286      * Notifies the {@link Conference} when the child calls should be swapped. Only invoked if the
287      * conference contains the capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
288      */
onSwap()289     public void onSwap() {}
290 
291     /**
292      * Notifies the {@link Conference} of a request to play a DTMF tone.
293      *
294      * @param c A DTMF character.
295      */
onPlayDtmfTone(char c)296     public void onPlayDtmfTone(char c) {}
297 
298     /**
299      * Notifies the {@link Conference} of a request to stop any currently playing DTMF tones.
300      */
onStopDtmfTone()301     public void onStopDtmfTone() {}
302 
303     /**
304      * Notifies the {@link Conference} that the {@link #getAudioState()} property has a new value.
305      *
306      * @param state The new call audio state.
307      * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead.
308      * @hide
309      */
310     @SystemApi
311     @Deprecated
onAudioStateChanged(AudioState state)312     public void onAudioStateChanged(AudioState state) {}
313 
314     /**
315      * Notifies the {@link Conference} that the {@link #getCallAudioState()} property has a new
316      * value.
317      *
318      * @param state The new call audio state.
319      */
onCallAudioStateChanged(CallAudioState state)320     public void onCallAudioStateChanged(CallAudioState state) {}
321 
322     /**
323      * Notifies the {@link Conference} that a {@link Connection} has been added to it.
324      *
325      * @param connection The newly added connection.
326      */
onConnectionAdded(Connection connection)327     public void onConnectionAdded(Connection connection) {}
328 
329     /**
330      * Notifies the {@link Conference} of a request to add a new participants to the conference call
331      * @param participants that will be added to this conference call
332      * @hide
333      */
onAddConferenceParticipants(@onNull List<Uri> participants)334     public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
335 
336     /**
337      * Notifies this Conference, which is in {@code STATE_RINGING}, of
338      * a request to accept.
339      * For managed {@link ConnectionService}s, this will be called when the user answers a call via
340      * the default dialer's {@link InCallService}.
341      *
342      * @param videoState The video state in which to answer the connection.
343      * @hide
344      */
onAnswer(int videoState)345     public void onAnswer(int videoState) {}
346 
347     /**
348      * Notifies this Conference, which is in {@code STATE_RINGING}, of
349      * a request to accept.
350      * For managed {@link ConnectionService}s, this will be called when the user answers a call via
351      * the default dialer's {@link InCallService}.
352      * @hide
353      */
onAnswer()354     public final void onAnswer() {
355          onAnswer(VideoProfile.STATE_AUDIO_ONLY);
356     }
357 
358     /**
359      * Notifies this Conference, which is in {@code STATE_RINGING}, of
360      * a request to reject.
361      * For managed {@link ConnectionService}s, this will be called when the user rejects a call via
362      * the default dialer's {@link InCallService}.
363      * @hide
364      */
onReject()365     public void onReject() {}
366 
367     /**
368      * Sets state to be on hold.
369      */
setOnHold()370     public final void setOnHold() {
371         setState(Connection.STATE_HOLDING);
372     }
373 
374     /**
375      * Sets state to be dialing.
376      */
setDialing()377     public final void setDialing() {
378         setState(Connection.STATE_DIALING);
379     }
380 
381     /**
382      * Sets state to be ringing.
383      * @hide
384      */
setRinging()385     public final void setRinging() {
386         setState(Connection.STATE_RINGING);
387     }
388 
389     /**
390      * Sets state to be active.
391      */
setActive()392     public final void setActive() {
393         setRingbackRequested(false);
394         setState(Connection.STATE_ACTIVE);
395     }
396 
397     /**
398      * Sets state to disconnected.
399      *
400      * @param disconnectCause The reason for the disconnection, as described by
401      *     {@link android.telecom.DisconnectCause}.
402      */
setDisconnected(DisconnectCause disconnectCause)403     public final void setDisconnected(DisconnectCause disconnectCause) {
404         mDisconnectCause = disconnectCause;;
405         setState(Connection.STATE_DISCONNECTED);
406         for (Listener l : mListeners) {
407             l.onDisconnected(this, mDisconnectCause);
408         }
409     }
410 
411     /**
412      * @return The {@link DisconnectCause} for this connection.
413      */
getDisconnectCause()414     public final DisconnectCause getDisconnectCause() {
415         return mDisconnectCause;
416     }
417 
418     /**
419      * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
420      * {@link Connection} for valid values.
421      *
422      * @param connectionCapabilities A bitmask of the {@code Capabilities} of the conference call.
423      */
setConnectionCapabilities(int connectionCapabilities)424     public final void setConnectionCapabilities(int connectionCapabilities) {
425         if (connectionCapabilities != mConnectionCapabilities) {
426             mConnectionCapabilities = connectionCapabilities;
427 
428             for (Listener l : mListeners) {
429                 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
430             }
431         }
432     }
433 
434     /**
435      * Sets the properties of a conference. See {@code PROPERTY_*} constants of class
436      * {@link Connection} for valid values.
437      *
438      * @param connectionProperties A bitmask of the {@code Properties} of the conference call.
439      */
setConnectionProperties(int connectionProperties)440     public final void setConnectionProperties(int connectionProperties) {
441         if (connectionProperties != mConnectionProperties) {
442             mConnectionProperties = connectionProperties;
443 
444             for (Listener l : mListeners) {
445                 l.onConnectionPropertiesChanged(this, mConnectionProperties);
446             }
447         }
448     }
449 
450     /**
451      * Adds the specified connection as a child of this conference.
452      *
453      * @param connection The connection to add.
454      * @return True if the connection was successfully added.
455      */
addConnection(Connection connection)456     public final boolean addConnection(Connection connection) {
457         Log.d(this, "Connection=%s, connection=", connection);
458         if (connection != null && !mChildConnections.contains(connection)) {
459             if (connection.setConference(this)) {
460                 mChildConnections.add(connection);
461                 onConnectionAdded(connection);
462                 for (Listener l : mListeners) {
463                     l.onConnectionAdded(this, connection);
464                 }
465                 return true;
466             }
467         }
468         return false;
469     }
470 
471     /**
472      * Removes the specified connection as a child of this conference.
473      *
474      * @param connection The connection to remove.
475      */
removeConnection(Connection connection)476     public final void removeConnection(Connection connection) {
477         Log.d(this, "removing %s from %s", connection, mChildConnections);
478         if (connection != null && mChildConnections.remove(connection)) {
479             connection.resetConference();
480             for (Listener l : mListeners) {
481                 l.onConnectionRemoved(this, connection);
482             }
483         }
484     }
485 
486     /**
487      * Sets the connections with which this connection can be conferenced.
488      *
489      * @param conferenceableConnections The set of connections this connection can conference with.
490      */
setConferenceableConnections(List<Connection> conferenceableConnections)491     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
492         clearConferenceableList();
493         for (Connection c : conferenceableConnections) {
494             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
495             // small amount of items here.
496             if (!mConferenceableConnections.contains(c)) {
497                 c.addConnectionListener(mConnectionDeathListener);
498                 mConferenceableConnections.add(c);
499             }
500         }
501         fireOnConferenceableConnectionsChanged();
502     }
503 
504     /**
505      * Requests that the framework play a ringback tone. This is to be invoked by implementations
506      * that do not play a ringback tone themselves in the conference's audio stream.
507      *
508      * @param ringback Whether the ringback tone is to be played.
509      * @hide
510      */
setRingbackRequested(boolean ringback)511     public final void setRingbackRequested(boolean ringback) {
512         if (mRingbackRequested != ringback) {
513             mRingbackRequested = ringback;
514             for (Listener l : mListeners) {
515                 l.onRingbackRequested(this, ringback);
516             }
517         }
518     }
519 
520     /**
521      * Set the video state for the conference.
522      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
523      * {@link VideoProfile#STATE_BIDIRECTIONAL},
524      * {@link VideoProfile#STATE_TX_ENABLED},
525      * {@link VideoProfile#STATE_RX_ENABLED}.
526      *
527      * @param videoState The new video state.
528      */
setVideoState(Connection c, int videoState)529     public final void setVideoState(Connection c, int videoState) {
530         Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
531                 this, c, videoState);
532         for (Listener l : mListeners) {
533             l.onVideoStateChanged(this, videoState);
534         }
535     }
536 
537     /**
538      * Sets the video connection provider.
539      *
540      * @param videoProvider The video provider.
541      */
setVideoProvider(Connection c, Connection.VideoProvider videoProvider)542     public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
543         Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
544                 this, c, videoProvider);
545         for (Listener l : mListeners) {
546             l.onVideoProviderChanged(this, videoProvider);
547         }
548     }
549 
fireOnConferenceableConnectionsChanged()550     private final void fireOnConferenceableConnectionsChanged() {
551         for (Listener l : mListeners) {
552             l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
553         }
554     }
555 
556     /**
557      * Returns the connections with which this connection can be conferenced.
558      */
getConferenceableConnections()559     public final List<Connection> getConferenceableConnections() {
560         return mUnmodifiableConferenceableConnections;
561     }
562 
563     /**
564      * Tears down the conference object and any of its current connections.
565      */
destroy()566     public final void destroy() {
567         Log.d(this, "destroying conference : %s", this);
568         // Tear down the children.
569         for (Connection connection : mChildConnections) {
570             Log.d(this, "removing connection %s", connection);
571             removeConnection(connection);
572         }
573 
574         // If not yet disconnected, set the conference call as disconnected first.
575         if (mState != Connection.STATE_DISCONNECTED) {
576             Log.d(this, "setting to disconnected");
577             setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
578         }
579 
580         // ...and notify.
581         for (Listener l : mListeners) {
582             l.onDestroyed(this);
583         }
584     }
585 
586     /**
587      * Add a listener to be notified of a state change.
588      *
589      * @param listener The new listener.
590      * @return This conference.
591      * @hide
592      */
addListener(Listener listener)593     final Conference addListener(Listener listener) {
594         mListeners.add(listener);
595         return this;
596     }
597 
598     /**
599      * Removes the specified listener.
600      *
601      * @param listener The listener to remove.
602      * @return This conference.
603      * @hide
604      */
removeListener(Listener listener)605     final Conference removeListener(Listener listener) {
606         mListeners.remove(listener);
607         return this;
608     }
609 
610     /**
611      * Retrieves the primary connection associated with the conference.  The primary connection is
612      * the connection from which the conference will retrieve its current state.
613      *
614      * @return The primary connection.
615      * @hide
616      */
617     @TestApi
618     @SystemApi
getPrimaryConnection()619     public Connection getPrimaryConnection() {
620         if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
621             return null;
622         }
623         return mUnmodifiableChildConnections.get(0);
624     }
625 
626     /**
627      * @hide
628      * @deprecated Use {@link #setConnectionTime}.
629      */
630     @Deprecated
631     @SystemApi
setConnectTimeMillis(long connectTimeMillis)632     public final void setConnectTimeMillis(long connectTimeMillis) {
633         setConnectionTime(connectTimeMillis);
634     }
635 
636     /**
637      * Sets the connection start time of the {@code Conference}.  This is used in the call log to
638      * indicate the date and time when the conference took place.
639      * <p>
640      * Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
641      * <p>
642      * When setting the connection time, you should always set the connection elapsed time via
643      * {@link #setConnectionStartElapsedRealtimeMillis(long)} to ensure the duration is reflected.
644      *
645      * @param connectionTimeMillis The connection time, in milliseconds, as returned by
646      *                             {@link System#currentTimeMillis()}.
647      */
setConnectionTime(@ntRangefrom = 0) long connectionTimeMillis)648     public final void setConnectionTime(@IntRange(from = 0) long connectionTimeMillis) {
649         mConnectTimeMillis = connectionTimeMillis;
650     }
651 
652     /**
653      * Sets the start time of the {@link Conference} which is the basis for the determining the
654      * duration of the {@link Conference}.
655      * <p>
656      * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
657      * zone changes do not impact the conference duration.
658      * <p>
659      * When setting this, you should also set the connection time via
660      * {@link #setConnectionTime(long)}.
661      *
662      * @param connectionStartElapsedRealTime The connection time, as measured by
663      * {@link SystemClock#elapsedRealtime()}.
664      * @deprecated use {@link #setConnectionStartElapsedRealtimeMillis(long)} instead.
665      */
666     @Deprecated
setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime)667     public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
668         setConnectionStartElapsedRealtimeMillis(connectionStartElapsedRealTime);
669     }
670 
671     /**
672      * Sets the start time of the {@link Conference} which is the basis for the determining the
673      * duration of the {@link Conference}.
674      * <p>
675      * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
676      * zone changes do not impact the conference duration.
677      * <p>
678      * When setting this, you should also set the connection time via
679      * {@link #setConnectionTime(long)}.
680      *
681      * @param connectionStartElapsedRealTime The connection time, as measured by
682      * {@link SystemClock#elapsedRealtime()}.
683      */
setConnectionStartElapsedRealtimeMillis( @lapsedRealtimeLong long connectionStartElapsedRealTime)684     public final void setConnectionStartElapsedRealtimeMillis(
685             @ElapsedRealtimeLong long connectionStartElapsedRealTime) {
686         mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
687     }
688 
689     /**
690      * @hide
691      * @deprecated Use {@link #getConnectionTime}.
692      */
693     @Deprecated
694     @SystemApi
getConnectTimeMillis()695     public final long getConnectTimeMillis() {
696         return getConnectionTime();
697     }
698 
699     /**
700      * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
701      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
702      * of the conference.
703      *
704      * @return The time at which the {@code Conference} was connected.
705      */
getConnectionTime()706     public final @IntRange(from = 0) long getConnectionTime() {
707         return mConnectTimeMillis;
708     }
709 
710     /**
711      * Retrieves the connection start time of the {@link Conference}, if specified.  A value of
712      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
713      * of the conference.
714      * <p>
715      * This is based on the value of {@link SystemClock#elapsedRealtime()} to ensure that it is not
716      * impacted by wall clock changes (user initiated, network initiated, time zone change, etc).
717      * <p>
718      * Note: This is only exposed for use by the Telephony framework which needs it to copy
719      * conference start times among conference participants.  It is exposed as a system API since it
720      * has no general use other than to the Telephony framework.
721      *
722      * @return The elapsed time at which the {@link Conference} was connected.
723      */
getConnectionStartElapsedRealtimeMillis()724     public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
725         return mConnectionStartElapsedRealTime;
726     }
727 
728     /**
729      * Inform this Conference that the state of its audio output has been changed externally.
730      *
731      * @param state The new audio state.
732      * @hide
733      */
setCallAudioState(CallAudioState state)734     final void setCallAudioState(CallAudioState state) {
735         Log.d(this, "setCallAudioState %s", state);
736         mCallAudioState = state;
737         onAudioStateChanged(getAudioState());
738         onCallAudioStateChanged(state);
739     }
740 
setState(int newState)741     private void setState(int newState) {
742         if (mState != newState) {
743             int oldState = mState;
744             mState = newState;
745             for (Listener l : mListeners) {
746                 l.onStateChanged(this, oldState, newState);
747             }
748         }
749     }
750 
751     private static class FailureSignalingConference extends Conference {
752         private boolean mImmutable = false;
FailureSignalingConference(DisconnectCause disconnectCause, PhoneAccountHandle phoneAccount)753         public FailureSignalingConference(DisconnectCause disconnectCause,
754                 PhoneAccountHandle phoneAccount) {
755             super(phoneAccount);
756             setDisconnected(disconnectCause);
757             mImmutable = true;
758         }
checkImmutable()759         public void checkImmutable() {
760             if (mImmutable) {
761                 throw new UnsupportedOperationException("Conference is immutable");
762             }
763         }
764     }
765 
766     /**
767      * Return a {@code Conference} which represents a failed conference attempt. The returned
768      * {@code Conference} will have a {@link android.telecom.DisconnectCause} and as specified,
769      * and a {@link #getState()} of {@code STATE_DISCONNECTED}.
770      * <p>
771      * The returned {@code Conference} can be assumed to {@link #destroy()} itself when appropriate,
772      * so users of this method need not maintain a reference to its return value to destroy it.
773      *
774      * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}).
775      * @return A {@code Conference} which indicates failure.
776      * @hide
777      */
createFailedConference( @onNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount)778     public @NonNull static Conference createFailedConference(
779             @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) {
780         return new FailureSignalingConference(disconnectCause, phoneAccount);
781     }
782 
clearConferenceableList()783     private final void clearConferenceableList() {
784         for (Connection c : mConferenceableConnections) {
785             c.removeConnectionListener(mConnectionDeathListener);
786         }
787         mConferenceableConnections.clear();
788     }
789 
790     @Override
toString()791     public String toString() {
792         return String.format(Locale.US,
793                 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s,"
794                 + "isRingbackRequested: %s, ThisObject %s]",
795                 Connection.stateToString(mState),
796                 Call.Details.capabilitiesToString(mConnectionCapabilities),
797                 getVideoState(),
798                 getVideoProvider(),
799                 isRingbackRequested() ? "Y" : "N",
800                 super.toString());
801     }
802 
803     /**
804      * Sets the label and icon status to display in the InCall UI.
805      *
806      * @param statusHints The status label and icon to set.
807      */
setStatusHints(StatusHints statusHints)808     public final void setStatusHints(StatusHints statusHints) {
809         mStatusHints = statusHints;
810         for (Listener l : mListeners) {
811             l.onStatusHintsChanged(this, statusHints);
812         }
813     }
814 
815     /**
816      * @return The status hints for this conference.
817      */
getStatusHints()818     public final StatusHints getStatusHints() {
819         return mStatusHints;
820     }
821 
822     /**
823      * Replaces all the extras associated with this {@code Conference}.
824      * <p>
825      * New or existing keys are replaced in the {@code Conference} extras.  Keys which are no longer
826      * in the new extras, but were present the last time {@code setExtras} was called are removed.
827      * <p>
828      * Alternatively you may use the {@link #putExtras(Bundle)}, and
829      * {@link #removeExtras(String...)} methods to modify the extras.
830      * <p>
831      * No assumptions should be made as to how an In-Call UI or service will handle these extras.
832      * Keys should be fully qualified (e.g., com.example.extras.MY_EXTRA) to avoid conflicts.
833      *
834      * @param extras The extras associated with this {@code Conference}.
835      */
setExtras(@ullable Bundle extras)836     public final void setExtras(@Nullable Bundle extras) {
837         // Keeping putExtras and removeExtras in the same lock so that this operation happens as a
838         // block instead of letting other threads put/remove while this method is running.
839         synchronized (mExtrasLock) {
840             // Add/replace any new or changed extras values.
841             putExtras(extras);
842             // If we have used "setExtras" in the past, compare the key set from the last invocation
843             // to the current one and remove any keys that went away.
844             if (mPreviousExtraKeys != null) {
845                 List<String> toRemove = new ArrayList<String>();
846                 for (String oldKey : mPreviousExtraKeys) {
847                     if (extras == null || !extras.containsKey(oldKey)) {
848                         toRemove.add(oldKey);
849                     }
850                 }
851 
852                 if (!toRemove.isEmpty()) {
853                     removeExtras(toRemove);
854                 }
855             }
856 
857             // Track the keys the last time set called setExtras.  This way, the next time setExtras
858             // is called we can see if the caller has removed any extras values.
859             if (mPreviousExtraKeys == null) {
860                 mPreviousExtraKeys = new ArraySet<String>();
861             }
862             mPreviousExtraKeys.clear();
863             if (extras != null) {
864                 mPreviousExtraKeys.addAll(extras.keySet());
865             }
866         }
867     }
868 
869     /**
870      * Adds some extras to this {@link Conference}.  Existing keys are replaced and new ones are
871      * added.
872      * <p>
873      * No assumptions should be made as to how an In-Call UI or service will handle these extras.
874      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
875      *
876      * @param extras The extras to add.
877      */
putExtras(@onNull Bundle extras)878     public final void putExtras(@NonNull Bundle extras) {
879         if (extras == null) {
880             return;
881         }
882 
883         // Creating a Bundle clone so we don't have to synchronize on mExtrasLock while calling
884         // onExtrasChanged.
885         Bundle listenersBundle;
886         synchronized (mExtrasLock) {
887             if (mExtras == null) {
888                 mExtras = new Bundle();
889             }
890             mExtras.putAll(extras);
891             listenersBundle = new Bundle(mExtras);
892         }
893 
894         for (Listener l : mListeners) {
895             l.onExtrasChanged(this, new Bundle(listenersBundle));
896         }
897     }
898 
899     /**
900      * Adds a boolean extra to this {@link Conference}.
901      *
902      * @param key The extra key.
903      * @param value The value.
904      * @hide
905      */
putExtra(String key, boolean value)906     public final void putExtra(String key, boolean value) {
907         Bundle newExtras = new Bundle();
908         newExtras.putBoolean(key, value);
909         putExtras(newExtras);
910     }
911 
912     /**
913      * Adds an integer extra to this {@link Conference}.
914      *
915      * @param key The extra key.
916      * @param value The value.
917      * @hide
918      */
putExtra(String key, int value)919     public final void putExtra(String key, int value) {
920         Bundle newExtras = new Bundle();
921         newExtras.putInt(key, value);
922         putExtras(newExtras);
923     }
924 
925     /**
926      * Adds a string extra to this {@link Conference}.
927      *
928      * @param key The extra key.
929      * @param value The value.
930      * @hide
931      */
putExtra(String key, String value)932     public final void putExtra(String key, String value) {
933         Bundle newExtras = new Bundle();
934         newExtras.putString(key, value);
935         putExtras(newExtras);
936     }
937 
938     /**
939      * Removes extras from this {@link Conference}.
940      *
941      * @param keys The keys of the extras to remove.
942      */
removeExtras(List<String> keys)943     public final void removeExtras(List<String> keys) {
944         if (keys == null || keys.isEmpty()) {
945             return;
946         }
947 
948         synchronized (mExtrasLock) {
949             if (mExtras != null) {
950                 for (String key : keys) {
951                     mExtras.remove(key);
952                 }
953             }
954         }
955 
956         List<String> unmodifiableKeys = Collections.unmodifiableList(keys);
957         for (Listener l : mListeners) {
958             l.onExtrasRemoved(this, unmodifiableKeys);
959         }
960     }
961 
962     /**
963      * Removes extras from this {@link Conference}.
964      *
965      * @param keys The keys of the extras to remove.
966      */
removeExtras(String .... keys)967     public final void removeExtras(String ... keys) {
968         removeExtras(Arrays.asList(keys));
969     }
970 
971     /**
972      * Returns the extras associated with this conference.
973      * <p>
974      * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}.
975      * <p>
976      * Telecom or an {@link InCallService} can also update the extras via
977      * {@link android.telecom.Call#putExtras(Bundle)}, and
978      * {@link Call#removeExtras(List)}.
979      * <p>
980      * The conference is notified of changes to the extras made by Telecom or an
981      * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
982      *
983      * @return The extras associated with this connection.
984      */
getExtras()985     public final Bundle getExtras() {
986         return mExtras;
987     }
988 
989     /**
990      * Notifies this {@link Conference} of a change to the extras made outside the
991      * {@link ConnectionService}.
992      * <p>
993      * These extras changes can originate from Telecom itself, or from an {@link InCallService} via
994      * {@link android.telecom.Call#putExtras(Bundle)}, and
995      * {@link Call#removeExtras(List)}.
996      *
997      * @param extras The new extras bundle.
998      */
onExtrasChanged(Bundle extras)999     public void onExtrasChanged(Bundle extras) {}
1000 
1001     /**
1002      * Set whether Telecom should treat this {@link Conference} as a multiparty conference call or
1003      * if it should treat it as a single-party call.
1004      * This method is used as part of a workaround regarding IMS conference calls and user
1005      * expectation.  In IMS, once a conference is formed, the UE is connected to an IMS conference
1006      * server.  If all participants of the conference drop out of the conference except for one, the
1007      * UE is still connected to the IMS conference server.  At this point, the user logically
1008      * assumes they're no longer in a conference, yet the underlying network actually is.
1009      * To help provide a better user experiece, IMS conference calls can pretend to actually be a
1010      * single-party call when the participant count drops to 1.  Although the dialer/phone app
1011      * could perform this trickery, it makes sense to do this in Telephony since a fix there will
1012      * ensure that bluetooth head units, auto and wearable apps all behave consistently.
1013      * <p>
1014      * This API is intended for use by the platform Telephony stack only.
1015      *
1016      * @param isConference {@code true} if this {@link Conference} should be treated like a
1017      *      conference call, {@code false} if it should be treated like a single-party call.
1018      * @hide
1019      */
1020     @SystemApi
1021     @TestApi
1022     @RequiresPermission(MODIFY_PHONE_STATE)
setConferenceState(boolean isConference)1023     public void setConferenceState(boolean isConference) {
1024         mIsMultiparty = isConference;
1025         for (Listener l : mListeners) {
1026             l.onConferenceStateChanged(this, isConference);
1027         }
1028     }
1029 
1030     /**
1031      * Sets the call direction of this {@link Conference}. By default, all {@link Conference}s have
1032      * a direction of {@link android.telecom.Call.Details.CallDirection#DIRECTION_UNKNOWN}. The
1033      * direction of a {@link Conference} is only applicable to the case where
1034      * {@link #setConferenceState(boolean)} has been set to {@code false}, otherwise the direction
1035      * will be ignored.
1036      * @param callDirection The direction of the conference.
1037      * @hide
1038      */
1039     @RequiresPermission(MODIFY_PHONE_STATE)
setCallDirection(@all.Details.CallDirection int callDirection)1040     public final void setCallDirection(@Call.Details.CallDirection int callDirection) {
1041         Log.d(this, "setDirection %d", callDirection);
1042         mCallDirection = callDirection;
1043         for (Listener l : mListeners) {
1044             l.onCallDirectionChanged(this, callDirection);
1045         }
1046     }
1047 
1048     /**
1049      * Determines if the {@link Conference} is considered "multiparty" or not.  By default all
1050      * conferences are considered multiparty.  A multiparty conference is one where there are
1051      * multiple conference participants (other than the host) in the conference.
1052      * This is tied to {@link #setConferenceState(boolean)}, which is used for some use cases to
1053      * have a conference appear as if it is a standalone call, in which case the conference will
1054      * no longer be multiparty.
1055      * @return {@code true} if conference is treated as a conference (i.e. it is multiparty),
1056      * {@code false} if it should emulate a standalone call (i.e. not multiparty).
1057      * @hide
1058      */
isMultiparty()1059     public boolean isMultiparty() {
1060         return mIsMultiparty;
1061     }
1062 
1063     /**
1064      * Sets the address of this {@link Conference}.  Used when {@link #setConferenceState(boolean)}
1065      * is called to mark a conference temporarily as NOT a conference.
1066      * <p>
1067      * Note: This is a Telephony-specific implementation detail related to IMS conferences.  It is
1068      * not intended for use outside of the Telephony stack.
1069      *
1070      * @param address The new address.
1071      * @param presentation The presentation requirements for the address.
1072      *        See {@link TelecomManager} for valid values.
1073      * @hide
1074      */
1075     @SystemApi
1076     @TestApi
1077     @RequiresPermission(MODIFY_PHONE_STATE)
setAddress(@onNull Uri address, @TelecomManager.Presentation int presentation)1078     public final void setAddress(@NonNull Uri address,
1079             @TelecomManager.Presentation int presentation) {
1080         Log.d(this, "setAddress %s", address);
1081         mAddress = address;
1082         mAddressPresentation = presentation;
1083         for (Listener l : mListeners) {
1084             l.onAddressChanged(this, address, presentation);
1085         }
1086     }
1087 
1088     /**
1089      * Returns the "address" associated with the conference.  This is applicable in two cases:
1090      * <ol>
1091      *     <li>When {@link #setConferenceState(boolean)} is used to mark a conference as
1092      *     temporarily "not a conference"; we need to present the correct address in the in-call
1093      *     UI.</li>
1094      *     <li>When the conference is not hosted on the current device, we need to know the address
1095      *     information for the purpose of showing the original address to the user, as well as for
1096      *     logging to the call log.</li>
1097      * </ol>
1098      * @return The address of the conference, or {@code null} if not applicable.
1099      * @hide
1100      */
getAddress()1101     public final Uri getAddress() {
1102         return mAddress;
1103     }
1104 
1105     /**
1106      * Returns the address presentation associated with the conference.
1107      * <p>
1108      * This is applicable in two cases:
1109      * <ol>
1110      *     <li>When {@link #setConferenceState(boolean)} is used to mark a conference as
1111      *     temporarily "not a conference"; we need to present the correct address presentation in
1112      *     the in-call UI.</li>
1113      *     <li>When the conference is not hosted on the current device, we need to know the address
1114      *     presentation information for the purpose of showing the original address to the user, as
1115      *     well as for logging to the call log.</li>
1116      * </ol>
1117      * @return The address presentation of the conference.
1118      * @hide
1119      */
getAddressPresentation()1120     public final @TelecomManager.Presentation int getAddressPresentation() {
1121         return mAddressPresentation;
1122     }
1123 
1124     /**
1125      * @return The caller display name (CNAP).
1126      * @hide
1127      */
getCallerDisplayName()1128     public final String getCallerDisplayName() {
1129         return mCallerDisplayName;
1130     }
1131 
1132     /**
1133      * @return The presentation requirements for the handle.
1134      *         See {@link TelecomManager} for valid values.
1135      * @hide
1136      */
getCallerDisplayNamePresentation()1137     public final int getCallerDisplayNamePresentation() {
1138         return mCallerDisplayNamePresentation;
1139     }
1140 
1141     /**
1142      * @return The call direction of this conference. Only applicable when
1143      * {@link #setConferenceState(boolean)} is set to false.
1144      * @hide
1145      */
getCallDirection()1146     public final @Call.Details.CallDirection int getCallDirection() {
1147         return mCallDirection;
1148     }
1149 
1150     /**
1151      * Sets the caller display name (CNAP) of this {@link Conference}.  Used when
1152      * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a
1153      * conference.
1154      * <p>
1155      * Note: This is a Telephony-specific implementation detail related to IMS conferences.  It is
1156      * not intended for use outside of the Telephony stack.
1157      *
1158      * @param callerDisplayName The new display name.
1159      * @param presentation The presentation requirements for the handle.
1160      *        See {@link TelecomManager} for valid values.
1161      * @hide
1162      */
1163     @SystemApi
1164     @TestApi
setCallerDisplayName(@onNull String callerDisplayName, @TelecomManager.Presentation int presentation)1165     public final void setCallerDisplayName(@NonNull String callerDisplayName,
1166             @TelecomManager.Presentation int presentation) {
1167         Log.d(this, "setCallerDisplayName %s", callerDisplayName);
1168         mCallerDisplayName = callerDisplayName;
1169         mCallerDisplayNamePresentation = presentation;
1170         for (Listener l : mListeners) {
1171             l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
1172         }
1173     }
1174 
1175     /**
1176      * Handles a change to extras received from Telecom.
1177      *
1178      * @param extras The new extras.
1179      * @hide
1180      */
handleExtrasChanged(Bundle extras)1181     final void handleExtrasChanged(Bundle extras) {
1182         Bundle b = null;
1183         synchronized (mExtrasLock) {
1184             mExtras = extras;
1185             if (mExtras != null) {
1186                 b = new Bundle(mExtras);
1187             }
1188         }
1189         onExtrasChanged(b);
1190     }
1191 
1192     /**
1193      * Sends an event associated with this {@link Conference} with associated event extras to the
1194      * {@link InCallService}.
1195      * <p>
1196      * Connection events are used to communicate point in time information from a
1197      * {@link ConnectionService} to an {@link InCallService} implementation.  An example of a
1198      * custom connection event includes notifying the UI when a WIFI call has been handed over to
1199      * LTE, which the InCall UI might use to inform the user that billing charges may apply.  The
1200      * Android Telephony framework will send the {@link Connection#EVENT_MERGE_COMPLETE}
1201      * connection event when a call to {@link Call#mergeConference()} has completed successfully.
1202      * <p>
1203      * Events are exposed to {@link InCallService} implementations via
1204      * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
1205      * <p>
1206      * No assumptions should be made as to how an In-Call UI or service will handle these events.
1207      * The {@link ConnectionService} must assume that the In-Call UI could even chose to ignore
1208      * some events altogether.
1209      * <p>
1210      * Events should be fully qualified (e.g. {@code com.example.event.MY_EVENT}) to avoid
1211      * conflicts between {@link ConnectionService} implementations.  Further, custom
1212      * {@link ConnectionService} implementations shall not re-purpose events in the
1213      * {@code android.*} namespace, nor shall they define new event types in this namespace.  When
1214      * defining a custom event type, ensure the contents of the extras {@link Bundle} is clearly
1215      * defined.  Extra keys for this bundle should be named similar to the event type (e.g.
1216      * {@code com.example.extra.MY_EXTRA}).
1217      * <p>
1218      * When defining events and the associated extras, it is important to keep their behavior
1219      * consistent when the associated {@link ConnectionService} is updated.  Support for deprecated
1220      * events/extras should me maintained to ensure backwards compatibility with older
1221      * {@link InCallService} implementations which were built to support the older behavior.
1222      * <p>
1223      * Expected connection events from the Telephony stack are:
1224      * <p>
1225      * <ul>
1226      *      <li>{@link Connection#EVENT_CALL_HOLD_FAILED} with {@code null} {@code extras} when the
1227      *      {@link Conference} could not be held.</li>
1228      *      <li>{@link Connection#EVENT_MERGE_START} with {@code null} {@code extras} when a new
1229      *      call is being merged into the conference.</li>
1230      *      <li>{@link Connection#EVENT_MERGE_COMPLETE} with {@code null} {@code extras} a new call
1231      *      has completed being merged into the conference.</li>
1232      *      <li>{@link Connection#EVENT_CALL_MERGE_FAILED} with {@code null} {@code extras} a new
1233      *      call has failed to merge into the conference (the dialer app can determine which call
1234      *      failed to merge based on the fact that the call still exists outside of the conference
1235      *      at the end of the merge process).</li>
1236      * </ul>
1237      *
1238      * @param event The conference event.
1239      * @param extras Optional bundle containing extra information associated with the event.
1240      */
sendConferenceEvent(@onNull String event, @Nullable Bundle extras)1241     public void sendConferenceEvent(@NonNull String event, @Nullable Bundle extras) {
1242         for (Listener l : mListeners) {
1243             l.onConnectionEvent(this, event, extras);
1244         }
1245     }
1246 }
1247