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