Conference.java revision 6b7f955c2d9b231660b8c54f8ef8e8e6ad802625
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.Nullable; 20import android.annotation.SystemApi; 21import android.os.Bundle; 22import android.telecom.Connection.VideoProvider; 23 24import java.util.ArrayList; 25import java.util.Collections; 26import java.util.List; 27import java.util.Locale; 28import java.util.Set; 29import java.util.concurrent.CopyOnWriteArrayList; 30import java.util.concurrent.CopyOnWriteArraySet; 31 32/** 33 * Represents a conference call which can contain any number of {@link Connection} objects. 34 */ 35public abstract class Conference extends Conferenceable { 36 37 /** 38 * Used to indicate that the conference connection time is not specified. If not specified, 39 * Telecom will set the connect time. 40 */ 41 public static final long CONNECT_TIME_NOT_SPECIFIED = 0; 42 43 /** @hide */ 44 public abstract static class Listener { 45 public void onStateChanged(Conference conference, int oldState, int newState) {} 46 public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {} 47 public void onConnectionAdded(Conference conference, Connection connection) {} 48 public void onConnectionRemoved(Conference conference, Connection connection) {} 49 public void onConferenceableConnectionsChanged( 50 Conference conference, List<Connection> conferenceableConnections) {} 51 public void onDestroyed(Conference conference) {} 52 public void onConnectionCapabilitiesChanged( 53 Conference conference, int connectionCapabilities) {} 54 public void onVideoStateChanged(Conference c, int videoState) { } 55 public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {} 56 public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {} 57 public void onExtrasChanged(Conference conference, Bundle extras) {} 58 } 59 60 private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); 61 private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>(); 62 private final List<Connection> mUnmodifiableChildConnections = 63 Collections.unmodifiableList(mChildConnections); 64 private final List<Connection> mConferenceableConnections = new ArrayList<>(); 65 private final List<Connection> mUnmodifiableConferenceableConnections = 66 Collections.unmodifiableList(mConferenceableConnections); 67 68 private PhoneAccountHandle mPhoneAccount; 69 private CallAudioState mCallAudioState; 70 private int mState = Connection.STATE_NEW; 71 private DisconnectCause mDisconnectCause; 72 private int mConnectionCapabilities; 73 private String mDisconnectMessage; 74 private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED; 75 private StatusHints mStatusHints; 76 private Bundle mExtras; 77 78 private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { 79 @Override 80 public void onDestroyed(Connection c) { 81 if (mConferenceableConnections.remove(c)) { 82 fireOnConferenceableConnectionsChanged(); 83 } 84 } 85 }; 86 87 /** 88 * Constructs a new Conference with a mandatory {@link PhoneAccountHandle} 89 * 90 * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference. 91 */ 92 public Conference(PhoneAccountHandle phoneAccount) { 93 mPhoneAccount = phoneAccount; 94 } 95 96 /** 97 * Returns the {@link PhoneAccountHandle} the conference call is being placed through. 98 * 99 * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference. 100 */ 101 public final PhoneAccountHandle getPhoneAccountHandle() { 102 return mPhoneAccount; 103 } 104 105 /** 106 * Returns the list of connections currently associated with the conference call. 107 * 108 * @return A list of {@code Connection} objects which represent the children of the conference. 109 */ 110 public final List<Connection> getConnections() { 111 return mUnmodifiableChildConnections; 112 } 113 114 /** 115 * Gets the state of the conference call. See {@link Connection} for valid values. 116 * 117 * @return A constant representing the state the conference call is currently in. 118 */ 119 public final int getState() { 120 return mState; 121 } 122 123 /** 124 * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class 125 * {@link Connection} for valid values. 126 * 127 * @return A bitmask of the capabilities of the conference call. 128 */ 129 public final int getConnectionCapabilities() { 130 return mConnectionCapabilities; 131 } 132 133 /** 134 * Whether the given capabilities support the specified capability. 135 * 136 * @param capabilities A capability bit field. 137 * @param capability The capability to check capabilities for. 138 * @return Whether the specified capability is supported. 139 * @hide 140 */ 141 public static boolean can(int capabilities, int capability) { 142 return (capabilities & capability) != 0; 143 } 144 145 /** 146 * Whether the capabilities of this {@code Connection} supports the specified capability. 147 * 148 * @param capability The capability to check capabilities for. 149 * @return Whether the specified capability is supported. 150 * @hide 151 */ 152 public boolean can(int capability) { 153 return can(mConnectionCapabilities, capability); 154 } 155 156 /** 157 * Removes the specified capability from the set of capabilities of this {@code Conference}. 158 * 159 * @param capability The capability to remove from the set. 160 * @hide 161 */ 162 public void removeCapability(int capability) { 163 mConnectionCapabilities &= ~capability; 164 } 165 166 /** 167 * Adds the specified capability to the set of capabilities of this {@code Conference}. 168 * 169 * @param capability The capability to add to the set. 170 * @hide 171 */ 172 public void addCapability(int capability) { 173 mConnectionCapabilities |= capability; 174 } 175 176 /** 177 * @return The audio state of the conference, describing how its audio is currently 178 * being routed by the system. This is {@code null} if this Conference 179 * does not directly know about its audio state. 180 * @deprecated Use {@link #getCallAudioState()} instead. 181 * @hide 182 */ 183 @Deprecated 184 @SystemApi 185 public final AudioState getAudioState() { 186 return new AudioState(mCallAudioState); 187 } 188 189 /** 190 * @return The audio state of the conference, describing how its audio is currently 191 * being routed by the system. This is {@code null} if this Conference 192 * does not directly know about its audio state. 193 */ 194 public final CallAudioState getCallAudioState() { 195 return mCallAudioState; 196 } 197 198 /** 199 * Returns VideoProvider of the primary call. This can be null. 200 */ 201 public VideoProvider getVideoProvider() { 202 return null; 203 } 204 205 /** 206 * Returns video state of the primary call. 207 */ 208 public int getVideoState() { 209 return VideoProfile.VideoState.AUDIO_ONLY; 210 } 211 212 /** 213 * Invoked when the Conference and all it's {@link Connection}s should be disconnected. 214 */ 215 public void onDisconnect() {} 216 217 /** 218 * Invoked when the specified {@link Connection} should be separated from the conference call. 219 * 220 * @param connection The connection to separate. 221 */ 222 public void onSeparate(Connection connection) {} 223 224 /** 225 * Invoked when the specified {@link Connection} should merged with the conference call. 226 * 227 * @param connection The {@code Connection} to merge. 228 */ 229 public void onMerge(Connection connection) {} 230 231 /** 232 * Invoked when the conference should be put on hold. 233 */ 234 public void onHold() {} 235 236 /** 237 * Invoked when the conference should be moved from hold to active. 238 */ 239 public void onUnhold() {} 240 241 /** 242 * Invoked when the child calls should be merged. Only invoked if the conference contains the 243 * capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}. 244 */ 245 public void onMerge() {} 246 247 /** 248 * Invoked when the child calls should be swapped. Only invoked if the conference contains the 249 * capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}. 250 */ 251 public void onSwap() {} 252 253 /** 254 * Notifies this conference of a request to play a DTMF tone. 255 * 256 * @param c A DTMF character. 257 */ 258 public void onPlayDtmfTone(char c) {} 259 260 /** 261 * Notifies this conference of a request to stop any currently playing DTMF tones. 262 */ 263 public void onStopDtmfTone() {} 264 265 /** 266 * Notifies this conference that the {@link #getAudioState()} property has a new value. 267 * 268 * @param state The new call audio state. 269 * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead. 270 * @hide 271 */ 272 @SystemApi 273 @Deprecated 274 public void onAudioStateChanged(AudioState state) {} 275 276 /** 277 * Notifies this conference that the {@link #getCallAudioState()} property has a new value. 278 * 279 * @param state The new call audio state. 280 */ 281 public void onCallAudioStateChanged(CallAudioState state) {} 282 283 /** 284 * Notifies this conference that a connection has been added to it. 285 * 286 * @param connection The newly added connection. 287 */ 288 public void onConnectionAdded(Connection connection) {} 289 290 /** 291 * Sets state to be on hold. 292 */ 293 public final void setOnHold() { 294 setState(Connection.STATE_HOLDING); 295 } 296 297 /** 298 * Sets state to be active. 299 */ 300 public final void setActive() { 301 setState(Connection.STATE_ACTIVE); 302 } 303 304 /** 305 * Sets state to disconnected. 306 * 307 * @param disconnectCause The reason for the disconnection, as described by 308 * {@link android.telecom.DisconnectCause}. 309 */ 310 public final void setDisconnected(DisconnectCause disconnectCause) { 311 mDisconnectCause = disconnectCause;; 312 setState(Connection.STATE_DISCONNECTED); 313 for (Listener l : mListeners) { 314 l.onDisconnected(this, mDisconnectCause); 315 } 316 } 317 318 /** 319 * @return The {@link DisconnectCause} for this connection. 320 */ 321 public final DisconnectCause getDisconnectCause() { 322 return mDisconnectCause; 323 } 324 325 /** 326 * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class 327 * {@link Connection} for valid values. 328 * 329 * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call. 330 */ 331 public final void setConnectionCapabilities(int connectionCapabilities) { 332 if (connectionCapabilities != mConnectionCapabilities) { 333 mConnectionCapabilities = connectionCapabilities; 334 335 for (Listener l : mListeners) { 336 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities); 337 } 338 } 339 } 340 341 /** 342 * Adds the specified connection as a child of this conference. 343 * 344 * @param connection The connection to add. 345 * @return True if the connection was successfully added. 346 */ 347 public final boolean addConnection(Connection connection) { 348 Log.d(this, "Connection=%s, connection=", connection); 349 if (connection != null && !mChildConnections.contains(connection)) { 350 if (connection.setConference(this)) { 351 mChildConnections.add(connection); 352 onConnectionAdded(connection); 353 for (Listener l : mListeners) { 354 l.onConnectionAdded(this, connection); 355 } 356 return true; 357 } 358 } 359 return false; 360 } 361 362 /** 363 * Removes the specified connection as a child of this conference. 364 * 365 * @param connection The connection to remove. 366 */ 367 public final void removeConnection(Connection connection) { 368 Log.d(this, "removing %s from %s", connection, mChildConnections); 369 if (connection != null && mChildConnections.remove(connection)) { 370 connection.resetConference(); 371 for (Listener l : mListeners) { 372 l.onConnectionRemoved(this, connection); 373 } 374 } 375 } 376 377 /** 378 * Sets the connections with which this connection can be conferenced. 379 * 380 * @param conferenceableConnections The set of connections this connection can conference with. 381 */ 382 public final void setConferenceableConnections(List<Connection> conferenceableConnections) { 383 clearConferenceableList(); 384 for (Connection c : conferenceableConnections) { 385 // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a 386 // small amount of items here. 387 if (!mConferenceableConnections.contains(c)) { 388 c.addConnectionListener(mConnectionDeathListener); 389 mConferenceableConnections.add(c); 390 } 391 } 392 fireOnConferenceableConnectionsChanged(); 393 } 394 395 /** 396 * Set the video state for the conference. 397 * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, 398 * {@link VideoProfile#STATE_BIDIRECTIONAL}, 399 * {@link VideoProfile#STATE_TX_ENABLED}, 400 * {@link VideoProfile#STATE_RX_ENABLED}. 401 * 402 * @param videoState The new video state. 403 */ 404 public final void setVideoState(Connection c, int videoState) { 405 Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s", 406 this, c, videoState); 407 for (Listener l : mListeners) { 408 l.onVideoStateChanged(this, videoState); 409 } 410 } 411 412 /** 413 * Sets the video connection provider. 414 * 415 * @param videoProvider The video provider. 416 */ 417 public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) { 418 Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s", 419 this, c, videoProvider); 420 for (Listener l : mListeners) { 421 l.onVideoProviderChanged(this, videoProvider); 422 } 423 } 424 425 private final void fireOnConferenceableConnectionsChanged() { 426 for (Listener l : mListeners) { 427 l.onConferenceableConnectionsChanged(this, getConferenceableConnections()); 428 } 429 } 430 431 /** 432 * Returns the connections with which this connection can be conferenced. 433 */ 434 public final List<Connection> getConferenceableConnections() { 435 return mUnmodifiableConferenceableConnections; 436 } 437 438 /** 439 * Tears down the conference object and any of its current connections. 440 */ 441 public final void destroy() { 442 Log.d(this, "destroying conference : %s", this); 443 // Tear down the children. 444 for (Connection connection : mChildConnections) { 445 Log.d(this, "removing connection %s", connection); 446 removeConnection(connection); 447 } 448 449 // If not yet disconnected, set the conference call as disconnected first. 450 if (mState != Connection.STATE_DISCONNECTED) { 451 Log.d(this, "setting to disconnected"); 452 setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); 453 } 454 455 // ...and notify. 456 for (Listener l : mListeners) { 457 l.onDestroyed(this); 458 } 459 } 460 461 /** 462 * Add a listener to be notified of a state change. 463 * 464 * @param listener The new listener. 465 * @return This conference. 466 * @hide 467 */ 468 public final Conference addListener(Listener listener) { 469 mListeners.add(listener); 470 return this; 471 } 472 473 /** 474 * Removes the specified listener. 475 * 476 * @param listener The listener to remove. 477 * @return This conference. 478 * @hide 479 */ 480 public final Conference removeListener(Listener listener) { 481 mListeners.remove(listener); 482 return this; 483 } 484 485 /** 486 * Retrieves the primary connection associated with the conference. The primary connection is 487 * the connection from which the conference will retrieve its current state. 488 * 489 * @return The primary connection. 490 * @hide 491 */ 492 @SystemApi 493 public Connection getPrimaryConnection() { 494 if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) { 495 return null; 496 } 497 return mUnmodifiableChildConnections.get(0); 498 } 499 500 /** 501 * @hide 502 * @deprecated Use {@link #setConnectionTime}. 503 */ 504 @Deprecated 505 @SystemApi 506 public final void setConnectTimeMillis(long connectTimeMillis) { 507 setConnectionTime(connectTimeMillis); 508 } 509 510 /** 511 * Sets the connection start time of the {@code Conference}. 512 * 513 * @param connectionTimeMillis The connection time, in milliseconds. 514 */ 515 public final void setConnectionTime(long connectionTimeMillis) { 516 mConnectTimeMillis = connectionTimeMillis; 517 } 518 519 /** 520 * @hide 521 * @deprecated Use {@link #getConnectionTime}. 522 */ 523 @Deprecated 524 @SystemApi 525 public final long getConnectTimeMillis() { 526 return getConnectionTime(); 527 } 528 529 /** 530 * Retrieves the connection start time of the {@code Conference}, if specified. A value of 531 * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time 532 * of the conference. 533 * 534 * @return The time at which the {@code Conference} was connected. 535 */ 536 public final long getConnectionTime() { 537 return mConnectTimeMillis; 538 } 539 540 /** 541 * Inform this Conference that the state of its audio output has been changed externally. 542 * 543 * @param state The new audio state. 544 * @hide 545 */ 546 final void setCallAudioState(CallAudioState state) { 547 Log.d(this, "setCallAudioState %s", state); 548 mCallAudioState = state; 549 onAudioStateChanged(getAudioState()); 550 onCallAudioStateChanged(state); 551 } 552 553 private void setState(int newState) { 554 if (newState != Connection.STATE_ACTIVE && 555 newState != Connection.STATE_HOLDING && 556 newState != Connection.STATE_DISCONNECTED) { 557 Log.w(this, "Unsupported state transition for Conference call.", 558 Connection.stateToString(newState)); 559 return; 560 } 561 562 if (mState != newState) { 563 int oldState = mState; 564 mState = newState; 565 for (Listener l : mListeners) { 566 l.onStateChanged(this, oldState, newState); 567 } 568 } 569 } 570 571 private final void clearConferenceableList() { 572 for (Connection c : mConferenceableConnections) { 573 c.removeConnectionListener(mConnectionDeathListener); 574 } 575 mConferenceableConnections.clear(); 576 } 577 578 @Override 579 public String toString() { 580 return String.format(Locale.US, 581 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", 582 Connection.stateToString(mState), 583 Call.Details.capabilitiesToString(mConnectionCapabilities), 584 getVideoState(), 585 getVideoProvider(), 586 super.toString()); 587 } 588 589 /** 590 * Sets the label and icon status to display in the InCall UI. 591 * 592 * @param statusHints The status label and icon to set. 593 */ 594 public final void setStatusHints(StatusHints statusHints) { 595 mStatusHints = statusHints; 596 for (Listener l : mListeners) { 597 l.onStatusHintsChanged(this, statusHints); 598 } 599 } 600 601 /** 602 * @return The status hints for this conference. 603 */ 604 public final StatusHints getStatusHints() { 605 return mStatusHints; 606 } 607 608 /** 609 * Set some extras that can be associated with this {@code Conference}. No assumptions should 610 * be made as to how an In-Call UI or service will handle these extras. 611 * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts. 612 * 613 * @param extras The extras associated with this {@code Connection}. 614 */ 615 public final void setExtras(@Nullable Bundle extras) { 616 mExtras = extras; 617 for (Listener l : mListeners) { 618 l.onExtrasChanged(this, extras); 619 } 620 } 621 622 /** 623 * @return The extras associated with this conference. 624 */ 625 public final Bundle getExtras() { 626 return mExtras; 627 } 628} 629