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