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