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