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.SystemApi; 20 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.List; 24 import java.util.Set; 25 import java.util.concurrent.CopyOnWriteArrayList; 26 import 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 33 public abstract class Conference { 34 35 /** @hide */ 36 public abstract static class Listener { onStateChanged(Conference conference, int oldState, int newState)37 public void onStateChanged(Conference conference, int oldState, int newState) {} onDisconnected(Conference conference, DisconnectCause disconnectCause)38 public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {} onConnectionAdded(Conference conference, Connection connection)39 public void onConnectionAdded(Conference conference, Connection connection) {} onConnectionRemoved(Conference conference, Connection connection)40 public void onConnectionRemoved(Conference conference, Connection connection) {} onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections)41 public void onConferenceableConnectionsChanged( 42 Conference conference, List<Connection> conferenceableConnections) {} onDestroyed(Conference conference)43 public void onDestroyed(Conference conference) {} onCapabilitiesChanged(Conference conference, int capabilities)44 public void onCapabilitiesChanged(Conference conference, int capabilities) {} 45 } 46 47 private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); 48 private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>(); 49 private final List<Connection> mUnmodifiableChildConnections = 50 Collections.unmodifiableList(mChildConnections); 51 private final List<Connection> mConferenceableConnections = new ArrayList<>(); 52 private final List<Connection> mUnmodifiableConferenceableConnections = 53 Collections.unmodifiableList(mConferenceableConnections); 54 55 private PhoneAccountHandle mPhoneAccount; 56 private AudioState mAudioState; 57 private int mState = Connection.STATE_NEW; 58 private DisconnectCause mDisconnectCause; 59 private int mCapabilities; 60 private String mDisconnectMessage; 61 62 private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { 63 @Override 64 public void onDestroyed(Connection c) { 65 if (mConferenceableConnections.remove(c)) { 66 fireOnConferenceableConnectionsChanged(); 67 } 68 } 69 }; 70 71 /** 72 * Constructs a new Conference with a mandatory {@link PhoneAccountHandle} 73 * 74 * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference. 75 */ Conference(PhoneAccountHandle phoneAccount)76 public Conference(PhoneAccountHandle phoneAccount) { 77 mPhoneAccount = phoneAccount; 78 } 79 80 /** 81 * Returns the {@link PhoneAccountHandle} the conference call is being placed through. 82 * 83 * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference. 84 */ getPhoneAccountHandle()85 public final PhoneAccountHandle getPhoneAccountHandle() { 86 return mPhoneAccount; 87 } 88 89 /** 90 * Returns the list of connections currently associated with the conference call. 91 * 92 * @return A list of {@code Connection} objects which represent the children of the conference. 93 */ getConnections()94 public final List<Connection> getConnections() { 95 return mUnmodifiableChildConnections; 96 } 97 98 /** 99 * Gets the state of the conference call. See {@link Connection} for valid values. 100 * 101 * @return A constant representing the state the conference call is currently in. 102 */ getState()103 public final int getState() { 104 return mState; 105 } 106 107 /** 108 * Returns the capabilities of a conference. See {@link PhoneCapabilities} for valid values. 109 * 110 * @return A bitmask of the {@code PhoneCapabilities} of the conference call. 111 */ getCapabilities()112 public final int getCapabilities() { 113 return mCapabilities; 114 } 115 116 /** 117 * @return The audio state of the conference, describing how its audio is currently 118 * being routed by the system. This is {@code null} if this Conference 119 * does not directly know about its audio state. 120 */ getAudioState()121 public final AudioState getAudioState() { 122 return mAudioState; 123 } 124 125 /** 126 * Invoked when the Conference and all it's {@link Connection}s should be disconnected. 127 */ onDisconnect()128 public void onDisconnect() {} 129 130 /** 131 * Invoked when the specified {@link Connection} should be separated from the conference call. 132 * 133 * @param connection The connection to separate. 134 */ onSeparate(Connection connection)135 public void onSeparate(Connection connection) {} 136 137 /** 138 * Invoked when the specified {@link Connection} should merged with the conference call. 139 * 140 * @param connection The {@code Connection} to merge. 141 */ onMerge(Connection connection)142 public void onMerge(Connection connection) {} 143 144 /** 145 * Invoked when the conference should be put on hold. 146 */ onHold()147 public void onHold() {} 148 149 /** 150 * Invoked when the conference should be moved from hold to active. 151 */ onUnhold()152 public void onUnhold() {} 153 154 /** 155 * Invoked when the child calls should be merged. Only invoked if the conference contains the 156 * capability {@link PhoneCapabilities#MERGE_CONFERENCE}. 157 */ onMerge()158 public void onMerge() {} 159 160 /** 161 * Invoked when the child calls should be swapped. Only invoked if the conference contains the 162 * capability {@link PhoneCapabilities#SWAP_CONFERENCE}. 163 */ onSwap()164 public void onSwap() {} 165 166 /** 167 * Notifies this conference of a request to play a DTMF tone. 168 * 169 * @param c A DTMF character. 170 */ onPlayDtmfTone(char c)171 public void onPlayDtmfTone(char c) {} 172 173 /** 174 * Notifies this conference of a request to stop any currently playing DTMF tones. 175 */ onStopDtmfTone()176 public void onStopDtmfTone() {} 177 178 /** 179 * Notifies this conference that the {@link #getAudioState()} property has a new value. 180 * 181 * @param state The new call audio state. 182 */ onAudioStateChanged(AudioState state)183 public void onAudioStateChanged(AudioState state) {} 184 185 /** 186 * Sets state to be on hold. 187 */ setOnHold()188 public final void setOnHold() { 189 setState(Connection.STATE_HOLDING); 190 } 191 192 /** 193 * Sets state to be active. 194 */ setActive()195 public final void setActive() { 196 setState(Connection.STATE_ACTIVE); 197 } 198 199 /** 200 * Sets state to disconnected. 201 * 202 * @param disconnectCause The reason for the disconnection, as described by 203 * {@link android.telecom.DisconnectCause}. 204 */ setDisconnected(DisconnectCause disconnectCause)205 public final void setDisconnected(DisconnectCause disconnectCause) { 206 mDisconnectCause = disconnectCause;; 207 setState(Connection.STATE_DISCONNECTED); 208 for (Listener l : mListeners) { 209 l.onDisconnected(this, mDisconnectCause); 210 } 211 } 212 213 /** 214 * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values. 215 * 216 * @param capabilities A bitmask of the {@code PhoneCapabilities} of the conference call. 217 */ setCapabilities(int capabilities)218 public final void setCapabilities(int capabilities) { 219 if (capabilities != mCapabilities) { 220 mCapabilities = capabilities; 221 222 for (Listener l : mListeners) { 223 l.onCapabilitiesChanged(this, mCapabilities); 224 } 225 } 226 } 227 228 /** 229 * Adds the specified connection as a child of this conference. 230 * 231 * @param connection The connection to add. 232 * @return True if the connection was successfully added. 233 */ addConnection(Connection connection)234 public final boolean addConnection(Connection connection) { 235 if (connection != null && !mChildConnections.contains(connection)) { 236 if (connection.setConference(this)) { 237 mChildConnections.add(connection); 238 for (Listener l : mListeners) { 239 l.onConnectionAdded(this, connection); 240 } 241 return true; 242 } 243 } 244 return false; 245 } 246 247 /** 248 * Removes the specified connection as a child of this conference. 249 * 250 * @param connection The connection to remove. 251 */ removeConnection(Connection connection)252 public final void removeConnection(Connection connection) { 253 Log.d(this, "removing %s from %s", connection, mChildConnections); 254 if (connection != null && mChildConnections.remove(connection)) { 255 connection.resetConference(); 256 for (Listener l : mListeners) { 257 l.onConnectionRemoved(this, connection); 258 } 259 } 260 } 261 262 /** 263 * Sets the connections with which this connection can be conferenced. 264 * 265 * @param conferenceableConnections The set of connections this connection can conference with. 266 */ setConferenceableConnections(List<Connection> conferenceableConnections)267 public final void setConferenceableConnections(List<Connection> conferenceableConnections) { 268 clearConferenceableList(); 269 for (Connection c : conferenceableConnections) { 270 // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a 271 // small amount of items here. 272 if (!mConferenceableConnections.contains(c)) { 273 c.addConnectionListener(mConnectionDeathListener); 274 mConferenceableConnections.add(c); 275 } 276 } 277 fireOnConferenceableConnectionsChanged(); 278 } 279 fireOnConferenceableConnectionsChanged()280 private final void fireOnConferenceableConnectionsChanged() { 281 for (Listener l : mListeners) { 282 l.onConferenceableConnectionsChanged(this, getConferenceableConnections()); 283 } 284 } 285 286 /** 287 * Returns the connections with which this connection can be conferenced. 288 */ getConferenceableConnections()289 public final List<Connection> getConferenceableConnections() { 290 return mUnmodifiableConferenceableConnections; 291 } 292 293 /** 294 * Tears down the conference object and any of its current connections. 295 */ destroy()296 public final void destroy() { 297 Log.d(this, "destroying conference : %s", this); 298 // Tear down the children. 299 for (Connection connection : mChildConnections) { 300 Log.d(this, "removing connection %s", connection); 301 removeConnection(connection); 302 } 303 304 // If not yet disconnected, set the conference call as disconnected first. 305 if (mState != Connection.STATE_DISCONNECTED) { 306 Log.d(this, "setting to disconnected"); 307 setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); 308 } 309 310 // ...and notify. 311 for (Listener l : mListeners) { 312 l.onDestroyed(this); 313 } 314 } 315 316 /** 317 * Add a listener to be notified of a state change. 318 * 319 * @param listener The new listener. 320 * @return This conference. 321 * @hide 322 */ addListener(Listener listener)323 public final Conference addListener(Listener listener) { 324 mListeners.add(listener); 325 return this; 326 } 327 328 /** 329 * Removes the specified listener. 330 * 331 * @param listener The listener to remove. 332 * @return This conference. 333 * @hide 334 */ removeListener(Listener listener)335 public final Conference removeListener(Listener listener) { 336 mListeners.remove(listener); 337 return this; 338 } 339 340 /** 341 * Inform this Conference that the state of its audio output has been changed externally. 342 * 343 * @param state The new audio state. 344 * @hide 345 */ setAudioState(AudioState state)346 final void setAudioState(AudioState state) { 347 Log.d(this, "setAudioState %s", state); 348 mAudioState = state; 349 onAudioStateChanged(state); 350 } 351 setState(int newState)352 private void setState(int newState) { 353 if (newState != Connection.STATE_ACTIVE && 354 newState != Connection.STATE_HOLDING && 355 newState != Connection.STATE_DISCONNECTED) { 356 Log.w(this, "Unsupported state transition for Conference call.", 357 Connection.stateToString(newState)); 358 return; 359 } 360 361 if (mState != newState) { 362 int oldState = mState; 363 mState = newState; 364 for (Listener l : mListeners) { 365 l.onStateChanged(this, oldState, newState); 366 } 367 } 368 } 369 clearConferenceableList()370 private final void clearConferenceableList() { 371 for (Connection c : mConferenceableConnections) { 372 c.removeConnectionListener(mConnectionDeathListener); 373 } 374 mConferenceableConnections.clear(); 375 } 376 } 377