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.Arrays; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.Objects; 32 import java.util.Set; 33 import java.util.concurrent.CopyOnWriteArrayList; 34 import java.util.concurrent.CopyOnWriteArraySet; 35 36 /** 37 * Represents a conference call which can contain any number of {@link Connection} objects. 38 */ 39 public 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 if (Objects.equals(mConferenceableConnections, conferenceableConnections)) { 458 return; 459 } 460 461 clearConferenceableList(); 462 for (Connection c : conferenceableConnections) { 463 // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a 464 // small amount of items here. 465 if (!mConferenceableConnections.contains(c)) { 466 c.addConnectionListener(mConnectionDeathListener); 467 mConferenceableConnections.add(c); 468 } 469 } 470 fireOnConferenceableConnectionsChanged(); 471 } 472 473 /** 474 * Set the video state for the conference. 475 * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, 476 * {@link VideoProfile#STATE_BIDIRECTIONAL}, 477 * {@link VideoProfile#STATE_TX_ENABLED}, 478 * {@link VideoProfile#STATE_RX_ENABLED}. 479 * 480 * @param videoState The new video state. 481 */ 482 public final void setVideoState(Connection c, int videoState) { 483 Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s", 484 this, c, videoState); 485 for (Listener l : mListeners) { 486 l.onVideoStateChanged(this, videoState); 487 } 488 } 489 490 /** 491 * Sets the video connection provider. 492 * 493 * @param videoProvider The video provider. 494 */ 495 public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) { 496 Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s", 497 this, c, videoProvider); 498 for (Listener l : mListeners) { 499 l.onVideoProviderChanged(this, videoProvider); 500 } 501 } 502 503 private final void fireOnConferenceableConnectionsChanged() { 504 for (Listener l : mListeners) { 505 l.onConferenceableConnectionsChanged(this, getConferenceableConnections()); 506 } 507 } 508 509 /** 510 * Returns the connections with which this connection can be conferenced. 511 */ 512 public final List<Connection> getConferenceableConnections() { 513 return mUnmodifiableConferenceableConnections; 514 } 515 516 /** 517 * Tears down the conference object and any of its current connections. 518 */ 519 public final void destroy() { 520 Log.d(this, "destroying conference : %s", this); 521 // Tear down the children. 522 for (Connection connection : mChildConnections) { 523 Log.d(this, "removing connection %s", connection); 524 removeConnection(connection); 525 } 526 527 // If not yet disconnected, set the conference call as disconnected first. 528 if (mState != Connection.STATE_DISCONNECTED) { 529 Log.d(this, "setting to disconnected"); 530 setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); 531 } 532 533 // ...and notify. 534 for (Listener l : mListeners) { 535 l.onDestroyed(this); 536 } 537 } 538 539 /** 540 * Add a listener to be notified of a state change. 541 * 542 * @param listener The new listener. 543 * @return This conference. 544 * @hide 545 */ 546 public final Conference addListener(Listener listener) { 547 mListeners.add(listener); 548 return this; 549 } 550 551 /** 552 * Removes the specified listener. 553 * 554 * @param listener The listener to remove. 555 * @return This conference. 556 * @hide 557 */ 558 public final Conference removeListener(Listener listener) { 559 mListeners.remove(listener); 560 return this; 561 } 562 563 /** 564 * Retrieves the primary connection associated with the conference. The primary connection is 565 * the connection from which the conference will retrieve its current state. 566 * 567 * @return The primary connection. 568 * @hide 569 */ 570 @SystemApi 571 public Connection getPrimaryConnection() { 572 if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) { 573 return null; 574 } 575 return mUnmodifiableChildConnections.get(0); 576 } 577 578 /** 579 * @hide 580 * @deprecated Use {@link #setConnectionTime}. 581 */ 582 @Deprecated 583 @SystemApi 584 public final void setConnectTimeMillis(long connectTimeMillis) { 585 setConnectionTime(connectTimeMillis); 586 } 587 588 /** 589 * Sets the connection start time of the {@code Conference}. 590 * 591 * @param connectionTimeMillis The connection time, in milliseconds. 592 */ 593 public final void setConnectionTime(long connectionTimeMillis) { 594 mConnectTimeMillis = connectionTimeMillis; 595 } 596 597 /** 598 * @hide 599 * @deprecated Use {@link #getConnectionTime}. 600 */ 601 @Deprecated 602 @SystemApi 603 public final long getConnectTimeMillis() { 604 return getConnectionTime(); 605 } 606 607 /** 608 * Retrieves the connection start time of the {@code Conference}, if specified. A value of 609 * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time 610 * of the conference. 611 * 612 * @return The time at which the {@code Conference} was connected. 613 */ 614 public final long getConnectionTime() { 615 return mConnectTimeMillis; 616 } 617 618 /** 619 * Inform this Conference that the state of its audio output has been changed externally. 620 * 621 * @param state The new audio state. 622 * @hide 623 */ 624 final void setCallAudioState(CallAudioState state) { 625 Log.d(this, "setCallAudioState %s", state); 626 mCallAudioState = state; 627 onAudioStateChanged(getAudioState()); 628 onCallAudioStateChanged(state); 629 } 630 631 private void setState(int newState) { 632 if (newState != Connection.STATE_ACTIVE && 633 newState != Connection.STATE_HOLDING && 634 newState != Connection.STATE_DISCONNECTED) { 635 Log.w(this, "Unsupported state transition for Conference call.", 636 Connection.stateToString(newState)); 637 return; 638 } 639 640 if (mState != newState) { 641 int oldState = mState; 642 mState = newState; 643 for (Listener l : mListeners) { 644 l.onStateChanged(this, oldState, newState); 645 } 646 } 647 } 648 649 private final void clearConferenceableList() { 650 for (Connection c : mConferenceableConnections) { 651 c.removeConnectionListener(mConnectionDeathListener); 652 } 653 mConferenceableConnections.clear(); 654 } 655 656 @Override 657 public String toString() { 658 return String.format(Locale.US, 659 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", 660 Connection.stateToString(mState), 661 Call.Details.capabilitiesToString(mConnectionCapabilities), 662 getVideoState(), 663 getVideoProvider(), 664 super.toString()); 665 } 666 667 /** 668 * Sets the label and icon status to display in the InCall UI. 669 * 670 * @param statusHints The status label and icon to set. 671 */ 672 public final void setStatusHints(StatusHints statusHints) { 673 mStatusHints = statusHints; 674 for (Listener l : mListeners) { 675 l.onStatusHintsChanged(this, statusHints); 676 } 677 } 678 679 /** 680 * @return The status hints for this conference. 681 */ 682 public final StatusHints getStatusHints() { 683 return mStatusHints; 684 } 685 686 /** 687 * Replaces all the extras associated with this {@code Conference}. 688 * <p> 689 * New or existing keys are replaced in the {@code Conference} extras. Keys which are no longer 690 * in the new extras, but were present the last time {@code setExtras} was called are removed. 691 * <p> 692 * Alternatively you may use the {@link #putExtras(Bundle)}, and 693 * {@link #removeExtras(String...)} methods to modify the extras. 694 * <p> 695 * No assumptions should be made as to how an In-Call UI or service will handle these extras. 696 * Keys should be fully qualified (e.g., com.example.extras.MY_EXTRA) to avoid conflicts. 697 * 698 * @param extras The extras associated with this {@code Conference}. 699 */ 700 public final void setExtras(@Nullable Bundle extras) { 701 // Keeping putExtras and removeExtras in the same lock so that this operation happens as a 702 // block instead of letting other threads put/remove while this method is running. 703 synchronized (mExtrasLock) { 704 // Add/replace any new or changed extras values. 705 putExtras(extras); 706 // If we have used "setExtras" in the past, compare the key set from the last invocation 707 // to the current one and remove any keys that went away. 708 if (mPreviousExtraKeys != null) { 709 List<String> toRemove = new ArrayList<String>(); 710 for (String oldKey : mPreviousExtraKeys) { 711 if (extras == null || !extras.containsKey(oldKey)) { 712 toRemove.add(oldKey); 713 } 714 } 715 716 if (!toRemove.isEmpty()) { 717 removeExtras(toRemove); 718 } 719 } 720 721 // Track the keys the last time set called setExtras. This way, the next time setExtras 722 // is called we can see if the caller has removed any extras values. 723 if (mPreviousExtraKeys == null) { 724 mPreviousExtraKeys = new ArraySet<String>(); 725 } 726 mPreviousExtraKeys.clear(); 727 if (extras != null) { 728 mPreviousExtraKeys.addAll(extras.keySet()); 729 } 730 } 731 } 732 733 /** 734 * Adds some extras to this {@link Conference}. Existing keys are replaced and new ones are 735 * added. 736 * <p> 737 * No assumptions should be made as to how an In-Call UI or service will handle these extras. 738 * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts. 739 * 740 * @param extras The extras to add. 741 */ 742 public final void putExtras(@NonNull Bundle extras) { 743 if (extras == null) { 744 return; 745 } 746 747 // Creating a Bundle clone so we don't have to synchronize on mExtrasLock while calling 748 // onExtrasChanged. 749 Bundle listenersBundle; 750 synchronized (mExtrasLock) { 751 if (mExtras == null) { 752 mExtras = new Bundle(); 753 } 754 mExtras.putAll(extras); 755 listenersBundle = new Bundle(mExtras); 756 } 757 758 for (Listener l : mListeners) { 759 l.onExtrasChanged(this, new Bundle(listenersBundle)); 760 } 761 } 762 763 /** 764 * Adds a boolean extra to this {@link Conference}. 765 * 766 * @param key The extra key. 767 * @param value The value. 768 * @hide 769 */ 770 public final void putExtra(String key, boolean value) { 771 Bundle newExtras = new Bundle(); 772 newExtras.putBoolean(key, value); 773 putExtras(newExtras); 774 } 775 776 /** 777 * Adds an integer extra to this {@link Conference}. 778 * 779 * @param key The extra key. 780 * @param value The value. 781 * @hide 782 */ 783 public final void putExtra(String key, int value) { 784 Bundle newExtras = new Bundle(); 785 newExtras.putInt(key, value); 786 putExtras(newExtras); 787 } 788 789 /** 790 * Adds a string extra to this {@link Conference}. 791 * 792 * @param key The extra key. 793 * @param value The value. 794 * @hide 795 */ 796 public final void putExtra(String key, String value) { 797 Bundle newExtras = new Bundle(); 798 newExtras.putString(key, value); 799 putExtras(newExtras); 800 } 801 802 /** 803 * Removes extras from this {@link Conference}. 804 * 805 * @param keys The keys of the extras to remove. 806 */ 807 public final void removeExtras(List<String> keys) { 808 if (keys == null || keys.isEmpty()) { 809 return; 810 } 811 812 synchronized (mExtrasLock) { 813 if (mExtras != null) { 814 for (String key : keys) { 815 mExtras.remove(key); 816 } 817 } 818 } 819 820 List<String> unmodifiableKeys = Collections.unmodifiableList(keys); 821 for (Listener l : mListeners) { 822 l.onExtrasRemoved(this, unmodifiableKeys); 823 } 824 } 825 826 /** 827 * Removes extras from this {@link Conference}. 828 * 829 * @param keys The keys of the extras to remove. 830 */ 831 public final void removeExtras(String ... keys) { 832 removeExtras(Arrays.asList(keys)); 833 } 834 835 /** 836 * Returns the extras associated with this conference. 837 * <p> 838 * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}. 839 * <p> 840 * Telecom or an {@link InCallService} can also update the extras via 841 * {@link android.telecom.Call#putExtras(Bundle)}, and 842 * {@link Call#removeExtras(List)}. 843 * <p> 844 * The conference is notified of changes to the extras made by Telecom or an 845 * {@link InCallService} by {@link #onExtrasChanged(Bundle)}. 846 * 847 * @return The extras associated with this connection. 848 */ 849 public final Bundle getExtras() { 850 return mExtras; 851 } 852 853 /** 854 * Notifies this {@link Conference} of a change to the extras made outside the 855 * {@link ConnectionService}. 856 * <p> 857 * These extras changes can originate from Telecom itself, or from an {@link InCallService} via 858 * {@link android.telecom.Call#putExtras(Bundle)}, and 859 * {@link Call#removeExtras(List)}. 860 * 861 * @param extras The new extras bundle. 862 */ 863 public void onExtrasChanged(Bundle extras) {} 864 865 /** 866 * Handles a change to extras received from Telecom. 867 * 868 * @param extras The new extras. 869 * @hide 870 */ 871 final void handleExtrasChanged(Bundle extras) { 872 Bundle b = null; 873 synchronized (mExtrasLock) { 874 mExtras = extras; 875 if (mExtras != null) { 876 b = new Bundle(mExtras); 877 } 878 } 879 onExtrasChanged(b); 880 } 881 } 882