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