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 com.android.internal.telecom.IConnectionService; 20 21import android.annotation.Nullable; 22import android.annotation.SystemApi; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.RemoteException; 26 27import java.util.ArrayList; 28import java.util.Collections; 29import java.util.List; 30import java.util.Set; 31import java.util.concurrent.CopyOnWriteArrayList; 32import java.util.concurrent.CopyOnWriteArraySet; 33 34/** 35 * A conference provided to a {@link ConnectionService} by another {@code ConnectionService} through 36 * {@link ConnectionService#conferenceRemoteConnections}. Once created, a {@code RemoteConference} 37 * can be used to control the conference call or monitor changes through 38 * {@link RemoteConnection.Callback}. 39 * 40 * @see ConnectionService#onRemoteConferenceAdded 41 */ 42public final class RemoteConference { 43 44 /** 45 * Callback base class for {@link RemoteConference}. 46 */ 47 public abstract static class Callback { 48 /** 49 * Invoked when the state of this {@code RemoteConferece} has changed. See 50 * {@link #getState()}. 51 * 52 * @param conference The {@code RemoteConference} invoking this method. 53 * @param oldState The previous state of the {@code RemoteConference}. 54 * @param newState The new state of the {@code RemoteConference}. 55 */ 56 public void onStateChanged(RemoteConference conference, int oldState, int newState) {} 57 58 /** 59 * Invoked when this {@code RemoteConference} is disconnected. 60 * 61 * @param conference The {@code RemoteConference} invoking this method. 62 * @param disconnectCause The ({@see DisconnectCause}) associated with this failed 63 * conference. 64 */ 65 public void onDisconnected(RemoteConference conference, DisconnectCause disconnectCause) {} 66 67 /** 68 * Invoked when a {@link RemoteConnection} is added to the conference call. 69 * 70 * @param conference The {@code RemoteConference} invoking this method. 71 * @param connection The {@link RemoteConnection} being added. 72 */ 73 public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {} 74 75 /** 76 * Invoked when a {@link RemoteConnection} is removed from the conference call. 77 * 78 * @param conference The {@code RemoteConference} invoking this method. 79 * @param connection The {@link RemoteConnection} being removed. 80 */ 81 public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {} 82 83 /** 84 * Indicates that the call capabilities of this {@code RemoteConference} have changed. 85 * See {@link #getConnectionCapabilities()}. 86 * 87 * @param conference The {@code RemoteConference} invoking this method. 88 * @param connectionCapabilities The new capabilities of the {@code RemoteConference}. 89 */ 90 public void onConnectionCapabilitiesChanged( 91 RemoteConference conference, 92 int connectionCapabilities) {} 93 94 /** 95 * Indicates that the call properties of this {@code RemoteConference} have changed. 96 * See {@link #getConnectionProperties()}. 97 * 98 * @param conference The {@code RemoteConference} invoking this method. 99 * @param connectionProperties The new properties of the {@code RemoteConference}. 100 * @hide 101 */ 102 public void onConnectionPropertiesChanged( 103 RemoteConference conference, 104 int connectionProperties) {} 105 106 107 /** 108 * Invoked when the set of {@link RemoteConnection}s which can be added to this conference 109 * call have changed. 110 * 111 * @param conference The {@code RemoteConference} invoking this method. 112 * @param conferenceableConnections The list of conferenceable {@link RemoteConnection}s. 113 */ 114 public void onConferenceableConnectionsChanged( 115 RemoteConference conference, 116 List<RemoteConnection> conferenceableConnections) {} 117 118 /** 119 * Indicates that this {@code RemoteConference} has been destroyed. No further requests 120 * should be made to the {@code RemoteConference}, and references to it should be cleared. 121 * 122 * @param conference The {@code RemoteConference} invoking this method. 123 */ 124 public void onDestroyed(RemoteConference conference) {} 125 126 /** 127 * Handles changes to the {@code RemoteConference} extras. 128 * 129 * @param conference The {@code RemoteConference} invoking this method. 130 * @param extras The extras containing other information associated with the conference. 131 */ 132 public void onExtrasChanged(RemoteConference conference, @Nullable Bundle extras) {} 133 } 134 135 private final String mId; 136 private final IConnectionService mConnectionService; 137 138 private final Set<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArraySet<>(); 139 private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>(); 140 private final List<RemoteConnection> mUnmodifiableChildConnections = 141 Collections.unmodifiableList(mChildConnections); 142 private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>(); 143 private final List<RemoteConnection> mUnmodifiableConferenceableConnections = 144 Collections.unmodifiableList(mConferenceableConnections); 145 146 private int mState = Connection.STATE_NEW; 147 private DisconnectCause mDisconnectCause; 148 private int mConnectionCapabilities; 149 private int mConnectionProperties; 150 private Bundle mExtras; 151 152 /** @hide */ 153 RemoteConference(String id, IConnectionService connectionService) { 154 mId = id; 155 mConnectionService = connectionService; 156 } 157 158 /** @hide */ 159 String getId() { 160 return mId; 161 } 162 163 /** @hide */ 164 void setDestroyed() { 165 for (RemoteConnection connection : mChildConnections) { 166 connection.setConference(null); 167 } 168 for (CallbackRecord<Callback> record : mCallbackRecords) { 169 final RemoteConference conference = this; 170 final Callback callback = record.getCallback(); 171 record.getHandler().post(new Runnable() { 172 @Override 173 public void run() { 174 callback.onDestroyed(conference); 175 } 176 }); 177 } 178 } 179 180 /** @hide */ 181 void setState(final int newState) { 182 if (newState != Connection.STATE_ACTIVE && 183 newState != Connection.STATE_HOLDING && 184 newState != Connection.STATE_DISCONNECTED) { 185 Log.w(this, "Unsupported state transition for Conference call.", 186 Connection.stateToString(newState)); 187 return; 188 } 189 190 if (mState != newState) { 191 final int oldState = mState; 192 mState = newState; 193 for (CallbackRecord<Callback> record : mCallbackRecords) { 194 final RemoteConference conference = this; 195 final Callback callback = record.getCallback(); 196 record.getHandler().post(new Runnable() { 197 @Override 198 public void run() { 199 callback.onStateChanged(conference, oldState, newState); 200 } 201 }); 202 } 203 } 204 } 205 206 /** @hide */ 207 void addConnection(final RemoteConnection connection) { 208 if (!mChildConnections.contains(connection)) { 209 mChildConnections.add(connection); 210 connection.setConference(this); 211 for (CallbackRecord<Callback> record : mCallbackRecords) { 212 final RemoteConference conference = this; 213 final Callback callback = record.getCallback(); 214 record.getHandler().post(new Runnable() { 215 @Override 216 public void run() { 217 callback.onConnectionAdded(conference, connection); 218 } 219 }); 220 } 221 } 222 } 223 224 /** @hide */ 225 void removeConnection(final RemoteConnection connection) { 226 if (mChildConnections.contains(connection)) { 227 mChildConnections.remove(connection); 228 connection.setConference(null); 229 for (CallbackRecord<Callback> record : mCallbackRecords) { 230 final RemoteConference conference = this; 231 final Callback callback = record.getCallback(); 232 record.getHandler().post(new Runnable() { 233 @Override 234 public void run() { 235 callback.onConnectionRemoved(conference, connection); 236 } 237 }); 238 } 239 } 240 } 241 242 /** @hide */ 243 void setConnectionCapabilities(final int connectionCapabilities) { 244 if (mConnectionCapabilities != connectionCapabilities) { 245 mConnectionCapabilities = connectionCapabilities; 246 for (CallbackRecord<Callback> record : mCallbackRecords) { 247 final RemoteConference conference = this; 248 final Callback callback = record.getCallback(); 249 record.getHandler().post(new Runnable() { 250 @Override 251 public void run() { 252 callback.onConnectionCapabilitiesChanged( 253 conference, mConnectionCapabilities); 254 } 255 }); 256 } 257 } 258 } 259 260 /** @hide */ 261 void setConnectionProperties(final int connectionProperties) { 262 if (mConnectionProperties != connectionProperties) { 263 mConnectionProperties = connectionProperties; 264 for (CallbackRecord<Callback> record : mCallbackRecords) { 265 final RemoteConference conference = this; 266 final Callback callback = record.getCallback(); 267 record.getHandler().post(new Runnable() { 268 @Override 269 public void run() { 270 callback.onConnectionPropertiesChanged( 271 conference, mConnectionProperties); 272 } 273 }); 274 } 275 } 276 } 277 278 /** @hide */ 279 void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) { 280 mConferenceableConnections.clear(); 281 mConferenceableConnections.addAll(conferenceableConnections); 282 for (CallbackRecord<Callback> record : mCallbackRecords) { 283 final RemoteConference conference = this; 284 final Callback callback = record.getCallback(); 285 record.getHandler().post(new Runnable() { 286 @Override 287 public void run() { 288 callback.onConferenceableConnectionsChanged( 289 conference, mUnmodifiableConferenceableConnections); 290 } 291 }); 292 } 293 } 294 295 /** @hide */ 296 void setDisconnected(final DisconnectCause disconnectCause) { 297 if (mState != Connection.STATE_DISCONNECTED) { 298 mDisconnectCause = disconnectCause; 299 setState(Connection.STATE_DISCONNECTED); 300 for (CallbackRecord<Callback> record : mCallbackRecords) { 301 final RemoteConference conference = this; 302 final Callback callback = record.getCallback(); 303 record.getHandler().post(new Runnable() { 304 @Override 305 public void run() { 306 callback.onDisconnected(conference, disconnectCause); 307 } 308 }); 309 } 310 } 311 } 312 313 /** @hide */ 314 void putExtras(final Bundle extras) { 315 if (mExtras == null) { 316 mExtras = new Bundle(); 317 } 318 mExtras.putAll(extras); 319 320 notifyExtrasChanged(); 321 } 322 323 /** @hide */ 324 void removeExtras(List<String> keys) { 325 if (mExtras == null || keys == null || keys.isEmpty()) { 326 return; 327 } 328 for (String key : keys) { 329 mExtras.remove(key); 330 } 331 332 notifyExtrasChanged(); 333 } 334 335 private void notifyExtrasChanged() { 336 for (CallbackRecord<Callback> record : mCallbackRecords) { 337 final RemoteConference conference = this; 338 final Callback callback = record.getCallback(); 339 record.getHandler().post(new Runnable() { 340 @Override 341 public void run() { 342 callback.onExtrasChanged(conference, mExtras); 343 } 344 }); 345 } 346 } 347 348 /** 349 * Returns the list of {@link RemoteConnection}s contained in this conference. 350 * 351 * @return A list of child connections. 352 */ 353 public final List<RemoteConnection> getConnections() { 354 return mUnmodifiableChildConnections; 355 } 356 357 /** 358 * Gets the state of the conference call. See {@link Connection} for valid values. 359 * 360 * @return A constant representing the state the conference call is currently in. 361 */ 362 public final int getState() { 363 return mState; 364 } 365 366 /** 367 * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class 368 * {@link Connection} for valid values. 369 * 370 * @return A bitmask of the capabilities of the conference call. 371 */ 372 public final int getConnectionCapabilities() { 373 return mConnectionCapabilities; 374 } 375 376 /** 377 * Returns the properties of the conference. See {@code PROPERTY_*} constants in class 378 * {@link Connection} for valid values. 379 * 380 * @return A bitmask of the properties of the conference call. 381 */ 382 public final int getConnectionProperties() { 383 return mConnectionProperties; 384 } 385 386 /** 387 * Obtain the extras associated with this {@code RemoteConnection}. 388 * 389 * @return The extras for this connection. 390 */ 391 public final Bundle getExtras() { 392 return mExtras; 393 } 394 395 /** 396 * Disconnects the conference call as well as the child {@link RemoteConnection}s. 397 */ 398 public void disconnect() { 399 try { 400 mConnectionService.disconnect(mId); 401 } catch (RemoteException e) { 402 } 403 } 404 405 /** 406 * Removes the specified {@link RemoteConnection} from the conference. This causes the 407 * {@link RemoteConnection} to become a standalone connection. This is a no-op if the 408 * {@link RemoteConnection} does not belong to this conference. 409 * 410 * @param connection The remote-connection to remove. 411 */ 412 public void separate(RemoteConnection connection) { 413 if (mChildConnections.contains(connection)) { 414 try { 415 mConnectionService.splitFromConference(connection.getId()); 416 } catch (RemoteException e) { 417 } 418 } 419 } 420 421 /** 422 * Merges all {@link RemoteConnection}s of this conference into a single call. This should be 423 * invoked only if the conference contains the capability 424 * {@link Connection#CAPABILITY_MERGE_CONFERENCE}, otherwise it is a no-op. The presence of said 425 * capability indicates that the connections of this conference, despite being part of the 426 * same conference object, are yet to have their audio streams merged; this is a common pattern 427 * for CDMA conference calls, but the capability is not used for GSM and SIP conference calls. 428 * Invoking this method will cause the unmerged child connections to merge their audio 429 * streams. 430 */ 431 public void merge() { 432 try { 433 mConnectionService.mergeConference(mId); 434 } catch (RemoteException e) { 435 } 436 } 437 438 /** 439 * Swaps the active audio stream between the conference's child {@link RemoteConnection}s. 440 * This should be invoked only if the conference contains the capability 441 * {@link Connection#CAPABILITY_SWAP_CONFERENCE}, otherwise it is a no-op. This is only used by 442 * {@link ConnectionService}s that create conferences for connections that do not yet have 443 * their audio streams merged; this is a common pattern for CDMA conference calls, but the 444 * capability is not used for GSM and SIP conference calls. Invoking this method will change the 445 * active audio stream to a different child connection. 446 */ 447 public void swap() { 448 try { 449 mConnectionService.swapConference(mId); 450 } catch (RemoteException e) { 451 } 452 } 453 454 /** 455 * Puts the conference on hold. 456 */ 457 public void hold() { 458 try { 459 mConnectionService.hold(mId); 460 } catch (RemoteException e) { 461 } 462 } 463 464 /** 465 * Unholds the conference call. 466 */ 467 public void unhold() { 468 try { 469 mConnectionService.unhold(mId); 470 } catch (RemoteException e) { 471 } 472 } 473 474 /** 475 * Returns the {@link DisconnectCause} for the conference if it is in the state 476 * {@link Connection#STATE_DISCONNECTED}. If the conference is not disconnected, this will 477 * return null. 478 * 479 * @return The disconnect cause. 480 */ 481 public DisconnectCause getDisconnectCause() { 482 return mDisconnectCause; 483 } 484 485 /** 486 * Requests that the conference start playing the specified DTMF tone. 487 * 488 * @param digit The digit for which to play a DTMF tone. 489 */ 490 public void playDtmfTone(char digit) { 491 try { 492 mConnectionService.playDtmfTone(mId, digit); 493 } catch (RemoteException e) { 494 } 495 } 496 497 /** 498 * Stops the most recent request to play a DTMF tone. 499 * 500 * @see #playDtmfTone 501 */ 502 public void stopDtmfTone() { 503 try { 504 mConnectionService.stopDtmfTone(mId); 505 } catch (RemoteException e) { 506 } 507 } 508 509 /** 510 * Request to change the conference's audio routing to the specified state. The specified state 511 * can include audio routing (Bluetooth, Speaker, etc) and muting state. 512 * 513 * @see android.telecom.AudioState 514 * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead. 515 * @hide 516 */ 517 @SystemApi 518 @Deprecated 519 public void setAudioState(AudioState state) { 520 setCallAudioState(new CallAudioState(state)); 521 } 522 523 /** 524 * Request to change the conference's audio routing to the specified state. The specified state 525 * can include audio routing (Bluetooth, Speaker, etc) and muting state. 526 */ 527 public void setCallAudioState(CallAudioState state) { 528 try { 529 mConnectionService.onCallAudioStateChanged(mId, state); 530 } catch (RemoteException e) { 531 } 532 } 533 534 535 /** 536 * Returns a list of independent connections that can me merged with this conference. 537 * 538 * @return A list of conferenceable connections. 539 */ 540 public List<RemoteConnection> getConferenceableConnections() { 541 return mUnmodifiableConferenceableConnections; 542 } 543 544 /** 545 * Register a callback through which to receive state updates for this conference. 546 * 547 * @param callback The callback to notify of state changes. 548 */ 549 public final void registerCallback(Callback callback) { 550 registerCallback(callback, new Handler()); 551 } 552 553 /** 554 * Registers a callback through which to receive state updates for this conference. 555 * Callbacks will be notified using the specified handler, if provided. 556 * 557 * @param callback The callback to notify of state changes. 558 * @param handler The handler on which to execute the callbacks. 559 */ 560 public final void registerCallback(Callback callback, Handler handler) { 561 unregisterCallback(callback); 562 if (callback != null && handler != null) { 563 mCallbackRecords.add(new CallbackRecord(callback, handler)); 564 } 565 } 566 567 /** 568 * Unregisters a previously registered callback. 569 * 570 * @see #registerCallback 571 * 572 * @param callback The callback to unregister. 573 */ 574 public final void unregisterCallback(Callback callback) { 575 if (callback != null) { 576 for (CallbackRecord<Callback> record : mCallbackRecords) { 577 if (record.getCallback() == callback) { 578 mCallbackRecords.remove(record); 579 break; 580 } 581 } 582 } 583 } 584} 585