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