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.Nullable; 20 import android.annotation.SystemApi; 21 import android.os.Bundle; 22 import android.telecom.Connection.VideoProvider; 23 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Set; 29 import java.util.concurrent.CopyOnWriteArrayList; 30 import java.util.concurrent.CopyOnWriteArraySet; 31 32 /** 33 * Represents a conference call which can contain any number of {@link Connection} objects. 34 */ 35 public 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 { onStateChanged(Conference conference, int oldState, int newState)45 public void onStateChanged(Conference conference, int oldState, int newState) {} onDisconnected(Conference conference, DisconnectCause disconnectCause)46 public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {} onConnectionAdded(Conference conference, Connection connection)47 public void onConnectionAdded(Conference conference, Connection connection) {} onConnectionRemoved(Conference conference, Connection connection)48 public void onConnectionRemoved(Conference conference, Connection connection) {} onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections)49 public void onConferenceableConnectionsChanged( 50 Conference conference, List<Connection> conferenceableConnections) {} onDestroyed(Conference conference)51 public void onDestroyed(Conference conference) {} onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities)52 public void onConnectionCapabilitiesChanged( 53 Conference conference, int connectionCapabilities) {} onVideoStateChanged(Conference c, int videoState)54 public void onVideoStateChanged(Conference c, int videoState) { } onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider)55 public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {} onStatusHintsChanged(Conference conference, StatusHints statusHints)56 public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {} onExtrasChanged(Conference conference, Bundle extras)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 */ Conference(PhoneAccountHandle phoneAccount)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 */ getPhoneAccountHandle()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 */ getConnections()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 */ getState()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 */ getConnectionCapabilities()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 */ can(int capabilities, int capability)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 */ can(int capability)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 */ removeCapability(int capability)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 */ addCapability(int capability)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 getAudioState()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 */ getCallAudioState()194 public final CallAudioState getCallAudioState() { 195 return mCallAudioState; 196 } 197 198 /** 199 * Returns VideoProvider of the primary call. This can be null. 200 */ getVideoProvider()201 public VideoProvider getVideoProvider() { 202 return null; 203 } 204 205 /** 206 * Returns video state of the primary call. 207 */ getVideoState()208 public int getVideoState() { 209 return VideoProfile.STATE_AUDIO_ONLY; 210 } 211 212 /** 213 * Invoked when the Conference and all it's {@link Connection}s should be disconnected. 214 */ onDisconnect()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 */ onSeparate(Connection connection)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 */ onMerge(Connection connection)229 public void onMerge(Connection connection) {} 230 231 /** 232 * Invoked when the conference should be put on hold. 233 */ onHold()234 public void onHold() {} 235 236 /** 237 * Invoked when the conference should be moved from hold to active. 238 */ onUnhold()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 */ onMerge()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 */ onSwap()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 */ onPlayDtmfTone(char c)258 public void onPlayDtmfTone(char c) {} 259 260 /** 261 * Notifies this conference of a request to stop any currently playing DTMF tones. 262 */ onStopDtmfTone()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 onAudioStateChanged(AudioState state)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 */ onCallAudioStateChanged(CallAudioState state)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 */ onConnectionAdded(Connection connection)288 public void onConnectionAdded(Connection connection) {} 289 290 /** 291 * Sets state to be on hold. 292 */ setOnHold()293 public final void setOnHold() { 294 setState(Connection.STATE_HOLDING); 295 } 296 297 /** 298 * Sets state to be dialing. 299 */ setDialing()300 public final void setDialing() { 301 setState(Connection.STATE_DIALING); 302 } 303 304 /** 305 * Sets state to be active. 306 */ setActive()307 public final void setActive() { 308 setState(Connection.STATE_ACTIVE); 309 } 310 311 /** 312 * Sets state to disconnected. 313 * 314 * @param disconnectCause The reason for the disconnection, as described by 315 * {@link android.telecom.DisconnectCause}. 316 */ setDisconnected(DisconnectCause disconnectCause)317 public final void setDisconnected(DisconnectCause disconnectCause) { 318 mDisconnectCause = disconnectCause;; 319 setState(Connection.STATE_DISCONNECTED); 320 for (Listener l : mListeners) { 321 l.onDisconnected(this, mDisconnectCause); 322 } 323 } 324 325 /** 326 * @return The {@link DisconnectCause} for this connection. 327 */ getDisconnectCause()328 public final DisconnectCause getDisconnectCause() { 329 return mDisconnectCause; 330 } 331 332 /** 333 * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class 334 * {@link Connection} for valid values. 335 * 336 * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call. 337 */ setConnectionCapabilities(int connectionCapabilities)338 public final void setConnectionCapabilities(int connectionCapabilities) { 339 if (connectionCapabilities != mConnectionCapabilities) { 340 mConnectionCapabilities = connectionCapabilities; 341 342 for (Listener l : mListeners) { 343 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities); 344 } 345 } 346 } 347 348 /** 349 * Adds the specified connection as a child of this conference. 350 * 351 * @param connection The connection to add. 352 * @return True if the connection was successfully added. 353 */ addConnection(Connection connection)354 public final boolean addConnection(Connection connection) { 355 Log.d(this, "Connection=%s, connection=", connection); 356 if (connection != null && !mChildConnections.contains(connection)) { 357 if (connection.setConference(this)) { 358 mChildConnections.add(connection); 359 onConnectionAdded(connection); 360 for (Listener l : mListeners) { 361 l.onConnectionAdded(this, connection); 362 } 363 return true; 364 } 365 } 366 return false; 367 } 368 369 /** 370 * Removes the specified connection as a child of this conference. 371 * 372 * @param connection The connection to remove. 373 */ removeConnection(Connection connection)374 public final void removeConnection(Connection connection) { 375 Log.d(this, "removing %s from %s", connection, mChildConnections); 376 if (connection != null && mChildConnections.remove(connection)) { 377 connection.resetConference(); 378 for (Listener l : mListeners) { 379 l.onConnectionRemoved(this, connection); 380 } 381 } 382 } 383 384 /** 385 * Sets the connections with which this connection can be conferenced. 386 * 387 * @param conferenceableConnections The set of connections this connection can conference with. 388 */ setConferenceableConnections(List<Connection> conferenceableConnections)389 public final void setConferenceableConnections(List<Connection> conferenceableConnections) { 390 clearConferenceableList(); 391 for (Connection c : conferenceableConnections) { 392 // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a 393 // small amount of items here. 394 if (!mConferenceableConnections.contains(c)) { 395 c.addConnectionListener(mConnectionDeathListener); 396 mConferenceableConnections.add(c); 397 } 398 } 399 fireOnConferenceableConnectionsChanged(); 400 } 401 402 /** 403 * Set the video state for the conference. 404 * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, 405 * {@link VideoProfile#STATE_BIDIRECTIONAL}, 406 * {@link VideoProfile#STATE_TX_ENABLED}, 407 * {@link VideoProfile#STATE_RX_ENABLED}. 408 * 409 * @param videoState The new video state. 410 */ setVideoState(Connection c, int videoState)411 public final void setVideoState(Connection c, int videoState) { 412 Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s", 413 this, c, videoState); 414 for (Listener l : mListeners) { 415 l.onVideoStateChanged(this, videoState); 416 } 417 } 418 419 /** 420 * Sets the video connection provider. 421 * 422 * @param videoProvider The video provider. 423 */ setVideoProvider(Connection c, Connection.VideoProvider videoProvider)424 public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) { 425 Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s", 426 this, c, videoProvider); 427 for (Listener l : mListeners) { 428 l.onVideoProviderChanged(this, videoProvider); 429 } 430 } 431 fireOnConferenceableConnectionsChanged()432 private final void fireOnConferenceableConnectionsChanged() { 433 for (Listener l : mListeners) { 434 l.onConferenceableConnectionsChanged(this, getConferenceableConnections()); 435 } 436 } 437 438 /** 439 * Returns the connections with which this connection can be conferenced. 440 */ getConferenceableConnections()441 public final List<Connection> getConferenceableConnections() { 442 return mUnmodifiableConferenceableConnections; 443 } 444 445 /** 446 * Tears down the conference object and any of its current connections. 447 */ destroy()448 public final void destroy() { 449 Log.d(this, "destroying conference : %s", this); 450 // Tear down the children. 451 for (Connection connection : mChildConnections) { 452 Log.d(this, "removing connection %s", connection); 453 removeConnection(connection); 454 } 455 456 // If not yet disconnected, set the conference call as disconnected first. 457 if (mState != Connection.STATE_DISCONNECTED) { 458 Log.d(this, "setting to disconnected"); 459 setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); 460 } 461 462 // ...and notify. 463 for (Listener l : mListeners) { 464 l.onDestroyed(this); 465 } 466 } 467 468 /** 469 * Add a listener to be notified of a state change. 470 * 471 * @param listener The new listener. 472 * @return This conference. 473 * @hide 474 */ addListener(Listener listener)475 public final Conference addListener(Listener listener) { 476 mListeners.add(listener); 477 return this; 478 } 479 480 /** 481 * Removes the specified listener. 482 * 483 * @param listener The listener to remove. 484 * @return This conference. 485 * @hide 486 */ removeListener(Listener listener)487 public final Conference removeListener(Listener listener) { 488 mListeners.remove(listener); 489 return this; 490 } 491 492 /** 493 * Retrieves the primary connection associated with the conference. The primary connection is 494 * the connection from which the conference will retrieve its current state. 495 * 496 * @return The primary connection. 497 * @hide 498 */ 499 @SystemApi getPrimaryConnection()500 public Connection getPrimaryConnection() { 501 if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) { 502 return null; 503 } 504 return mUnmodifiableChildConnections.get(0); 505 } 506 507 /** 508 * @hide 509 * @deprecated Use {@link #setConnectionTime}. 510 */ 511 @Deprecated 512 @SystemApi setConnectTimeMillis(long connectTimeMillis)513 public final void setConnectTimeMillis(long connectTimeMillis) { 514 setConnectionTime(connectTimeMillis); 515 } 516 517 /** 518 * Sets the connection start time of the {@code Conference}. 519 * 520 * @param connectionTimeMillis The connection time, in milliseconds. 521 */ setConnectionTime(long connectionTimeMillis)522 public final void setConnectionTime(long connectionTimeMillis) { 523 mConnectTimeMillis = connectionTimeMillis; 524 } 525 526 /** 527 * @hide 528 * @deprecated Use {@link #getConnectionTime}. 529 */ 530 @Deprecated 531 @SystemApi getConnectTimeMillis()532 public final long getConnectTimeMillis() { 533 return getConnectionTime(); 534 } 535 536 /** 537 * Retrieves the connection start time of the {@code Conference}, if specified. A value of 538 * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time 539 * of the conference. 540 * 541 * @return The time at which the {@code Conference} was connected. 542 */ getConnectionTime()543 public final long getConnectionTime() { 544 return mConnectTimeMillis; 545 } 546 547 /** 548 * Inform this Conference that the state of its audio output has been changed externally. 549 * 550 * @param state The new audio state. 551 * @hide 552 */ setCallAudioState(CallAudioState state)553 final void setCallAudioState(CallAudioState state) { 554 Log.d(this, "setCallAudioState %s", state); 555 mCallAudioState = state; 556 onAudioStateChanged(getAudioState()); 557 onCallAudioStateChanged(state); 558 } 559 setState(int newState)560 private void setState(int newState) { 561 if (newState != Connection.STATE_ACTIVE && 562 newState != Connection.STATE_HOLDING && 563 newState != Connection.STATE_DISCONNECTED) { 564 Log.w(this, "Unsupported state transition for Conference call.", 565 Connection.stateToString(newState)); 566 return; 567 } 568 569 if (mState != newState) { 570 int oldState = mState; 571 mState = newState; 572 for (Listener l : mListeners) { 573 l.onStateChanged(this, oldState, newState); 574 } 575 } 576 } 577 clearConferenceableList()578 private final void clearConferenceableList() { 579 for (Connection c : mConferenceableConnections) { 580 c.removeConnectionListener(mConnectionDeathListener); 581 } 582 mConferenceableConnections.clear(); 583 } 584 585 @Override toString()586 public String toString() { 587 return String.format(Locale.US, 588 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", 589 Connection.stateToString(mState), 590 Call.Details.capabilitiesToString(mConnectionCapabilities), 591 getVideoState(), 592 getVideoProvider(), 593 super.toString()); 594 } 595 596 /** 597 * Sets the label and icon status to display in the InCall UI. 598 * 599 * @param statusHints The status label and icon to set. 600 */ setStatusHints(StatusHints statusHints)601 public final void setStatusHints(StatusHints statusHints) { 602 mStatusHints = statusHints; 603 for (Listener l : mListeners) { 604 l.onStatusHintsChanged(this, statusHints); 605 } 606 } 607 608 /** 609 * @return The status hints for this conference. 610 */ getStatusHints()611 public final StatusHints getStatusHints() { 612 return mStatusHints; 613 } 614 615 /** 616 * Set some extras that can be associated with this {@code Conference}. No assumptions should 617 * be made as to how an In-Call UI or service will handle these extras. 618 * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts. 619 * 620 * @param extras The extras associated with this {@code Connection}. 621 */ setExtras(@ullable Bundle extras)622 public final void setExtras(@Nullable Bundle extras) { 623 mExtras = extras; 624 for (Listener l : mListeners) { 625 l.onExtrasChanged(this, extras); 626 } 627 } 628 629 /** 630 * @return The extras associated with this conference. 631 */ getExtras()632 public final Bundle getExtras() { 633 return mExtras; 634 } 635 } 636