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