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