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