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