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