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