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