|  | /* | 
|  | * Copyright (C) 2014 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.telecom; | 
|  |  | 
|  | import com.android.internal.telecom.IConnectionService; | 
|  |  | 
|  | import android.annotation.Nullable; | 
|  | import android.annotation.SystemApi; | 
|  | import android.os.Bundle; | 
|  | import android.os.Handler; | 
|  | import android.os.RemoteException; | 
|  |  | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.CopyOnWriteArrayList; | 
|  | import java.util.concurrent.CopyOnWriteArraySet; | 
|  |  | 
|  | /** | 
|  | * A conference provided to a {@link ConnectionService} by another {@code ConnectionService} through | 
|  | * {@link ConnectionService#conferenceRemoteConnections}. Once created, a {@code RemoteConference} | 
|  | * can be used to control the conference call or monitor changes through | 
|  | * {@link RemoteConnection.Callback}. | 
|  | * | 
|  | * @see ConnectionService#onRemoteConferenceAdded | 
|  | */ | 
|  | public final class RemoteConference { | 
|  |  | 
|  | /** | 
|  | * Callback base class for {@link RemoteConference}. | 
|  | */ | 
|  | public abstract static class Callback { | 
|  | /** | 
|  | * Invoked when the state of this {@code RemoteConferece} has changed. See | 
|  | * {@link #getState()}. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param oldState The previous state of the {@code RemoteConference}. | 
|  | * @param newState The new state of the {@code RemoteConference}. | 
|  | */ | 
|  | public void onStateChanged(RemoteConference conference, int oldState, int newState) {} | 
|  |  | 
|  | /** | 
|  | * Invoked when this {@code RemoteConference} is disconnected. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param disconnectCause The ({@see DisconnectCause}) associated with this failed | 
|  | *     conference. | 
|  | */ | 
|  | public void onDisconnected(RemoteConference conference, DisconnectCause disconnectCause) {} | 
|  |  | 
|  | /** | 
|  | * Invoked when a {@link RemoteConnection} is added to the conference call. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param connection The {@link RemoteConnection} being added. | 
|  | */ | 
|  | public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {} | 
|  |  | 
|  | /** | 
|  | * Invoked when a {@link RemoteConnection} is removed from the conference call. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param connection The {@link RemoteConnection} being removed. | 
|  | */ | 
|  | public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {} | 
|  |  | 
|  | /** | 
|  | * Indicates that the call capabilities of this {@code RemoteConference} have changed. | 
|  | * See {@link #getConnectionCapabilities()}. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param connectionCapabilities The new capabilities of the {@code RemoteConference}. | 
|  | */ | 
|  | public void onConnectionCapabilitiesChanged( | 
|  | RemoteConference conference, | 
|  | int connectionCapabilities) {} | 
|  |  | 
|  | /** | 
|  | * Indicates that the call properties of this {@code RemoteConference} have changed. | 
|  | * See {@link #getConnectionProperties()}. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param connectionProperties The new properties of the {@code RemoteConference}. | 
|  | */ | 
|  | public void onConnectionPropertiesChanged( | 
|  | RemoteConference conference, | 
|  | int connectionProperties) {} | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Invoked when the set of {@link RemoteConnection}s which can be added to this conference | 
|  | * call have changed. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param conferenceableConnections The list of conferenceable {@link RemoteConnection}s. | 
|  | */ | 
|  | public void onConferenceableConnectionsChanged( | 
|  | RemoteConference conference, | 
|  | List<RemoteConnection> conferenceableConnections) {} | 
|  |  | 
|  | /** | 
|  | * Indicates that this {@code RemoteConference} has been destroyed. No further requests | 
|  | * should be made to the {@code RemoteConference}, and references to it should be cleared. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | */ | 
|  | public void onDestroyed(RemoteConference conference) {} | 
|  |  | 
|  | /** | 
|  | * Handles changes to the {@code RemoteConference} extras. | 
|  | * | 
|  | * @param conference The {@code RemoteConference} invoking this method. | 
|  | * @param extras The extras containing other information associated with the conference. | 
|  | */ | 
|  | public void onExtrasChanged(RemoteConference conference, @Nullable Bundle extras) {} | 
|  | } | 
|  |  | 
|  | private final String mId; | 
|  | private final IConnectionService mConnectionService; | 
|  |  | 
|  | private final Set<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArraySet<>(); | 
|  | private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>(); | 
|  | private final List<RemoteConnection> mUnmodifiableChildConnections = | 
|  | Collections.unmodifiableList(mChildConnections); | 
|  | private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>(); | 
|  | private final List<RemoteConnection> mUnmodifiableConferenceableConnections = | 
|  | Collections.unmodifiableList(mConferenceableConnections); | 
|  |  | 
|  | private int mState = Connection.STATE_NEW; | 
|  | private DisconnectCause mDisconnectCause; | 
|  | private int mConnectionCapabilities; | 
|  | private int mConnectionProperties; | 
|  | private Bundle mExtras; | 
|  |  | 
|  | /** @hide */ | 
|  | RemoteConference(String id, IConnectionService connectionService) { | 
|  | mId = id; | 
|  | mConnectionService = connectionService; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | String getId() { | 
|  | return mId; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setDestroyed() { | 
|  | for (RemoteConnection connection : mChildConnections) { | 
|  | connection.setConference(null); | 
|  | } | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onDestroyed(conference); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setState(final int newState) { | 
|  | if (newState != Connection.STATE_ACTIVE && | 
|  | newState != Connection.STATE_HOLDING && | 
|  | newState != Connection.STATE_DISCONNECTED) { | 
|  | Log.w(this, "Unsupported state transition for Conference call.", | 
|  | Connection.stateToString(newState)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (mState != newState) { | 
|  | final int oldState = mState; | 
|  | mState = newState; | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onStateChanged(conference, oldState, newState); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void addConnection(final RemoteConnection connection) { | 
|  | if (!mChildConnections.contains(connection)) { | 
|  | mChildConnections.add(connection); | 
|  | connection.setConference(this); | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onConnectionAdded(conference, connection); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void removeConnection(final RemoteConnection connection) { | 
|  | if (mChildConnections.contains(connection)) { | 
|  | mChildConnections.remove(connection); | 
|  | connection.setConference(null); | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onConnectionRemoved(conference, connection); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setConnectionCapabilities(final int connectionCapabilities) { | 
|  | if (mConnectionCapabilities != connectionCapabilities) { | 
|  | mConnectionCapabilities = connectionCapabilities; | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onConnectionCapabilitiesChanged( | 
|  | conference, mConnectionCapabilities); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setConnectionProperties(final int connectionProperties) { | 
|  | if (mConnectionProperties != connectionProperties) { | 
|  | mConnectionProperties = connectionProperties; | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onConnectionPropertiesChanged( | 
|  | conference, mConnectionProperties); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) { | 
|  | mConferenceableConnections.clear(); | 
|  | mConferenceableConnections.addAll(conferenceableConnections); | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onConferenceableConnectionsChanged( | 
|  | conference, mUnmodifiableConferenceableConnections); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void setDisconnected(final DisconnectCause disconnectCause) { | 
|  | if (mState != Connection.STATE_DISCONNECTED) { | 
|  | mDisconnectCause = disconnectCause; | 
|  | setState(Connection.STATE_DISCONNECTED); | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onDisconnected(conference, disconnectCause); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void putExtras(final Bundle extras) { | 
|  | if (mExtras == null) { | 
|  | mExtras = new Bundle(); | 
|  | } | 
|  | mExtras.putAll(extras); | 
|  |  | 
|  | notifyExtrasChanged(); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | void removeExtras(List<String> keys) { | 
|  | if (mExtras == null || keys == null || keys.isEmpty()) { | 
|  | return; | 
|  | } | 
|  | for (String key : keys) { | 
|  | mExtras.remove(key); | 
|  | } | 
|  |  | 
|  | notifyExtrasChanged(); | 
|  | } | 
|  |  | 
|  | private void notifyExtrasChanged() { | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | final RemoteConference conference = this; | 
|  | final Callback callback = record.getCallback(); | 
|  | record.getHandler().post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | callback.onExtrasChanged(conference, mExtras); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the list of {@link RemoteConnection}s contained in this conference. | 
|  | * | 
|  | * @return A list of child connections. | 
|  | */ | 
|  | public final List<RemoteConnection> getConnections() { | 
|  | return mUnmodifiableChildConnections; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the state of the conference call. See {@link Connection} for valid values. | 
|  | * | 
|  | * @return A constant representing the state the conference call is currently in. | 
|  | */ | 
|  | public final int getState() { | 
|  | return mState; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class | 
|  | * {@link Connection} for valid values. | 
|  | * | 
|  | * @return A bitmask of the capabilities of the conference call. | 
|  | */ | 
|  | public final int getConnectionCapabilities() { | 
|  | return mConnectionCapabilities; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the properties of the conference. See {@code PROPERTY_*} constants in class | 
|  | * {@link Connection} for valid values. | 
|  | * | 
|  | * @return A bitmask of the properties of the conference call. | 
|  | */ | 
|  | public final int getConnectionProperties() { | 
|  | return mConnectionProperties; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Obtain the extras associated with this {@code RemoteConnection}. | 
|  | * | 
|  | * @return The extras for this connection. | 
|  | */ | 
|  | public final Bundle getExtras() { | 
|  | return mExtras; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Disconnects the conference call as well as the child {@link RemoteConnection}s. | 
|  | */ | 
|  | public void disconnect() { | 
|  | try { | 
|  | mConnectionService.disconnect(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Removes the specified {@link RemoteConnection} from the conference. This causes the | 
|  | * {@link RemoteConnection} to become a standalone connection. This is a no-op if the | 
|  | * {@link RemoteConnection} does not belong to this conference. | 
|  | * | 
|  | * @param connection The remote-connection to remove. | 
|  | */ | 
|  | public void separate(RemoteConnection connection) { | 
|  | if (mChildConnections.contains(connection)) { | 
|  | try { | 
|  | mConnectionService.splitFromConference(connection.getId()); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Merges all {@link RemoteConnection}s of this conference into a single call. This should be | 
|  | * invoked only if the conference contains the capability | 
|  | * {@link Connection#CAPABILITY_MERGE_CONFERENCE}, otherwise it is a no-op. The presence of said | 
|  | * capability indicates that the connections of this conference, despite being part of the | 
|  | * same conference object, are yet to have their audio streams merged; this is a common pattern | 
|  | * for CDMA conference calls, but the capability is not used for GSM and SIP conference calls. | 
|  | * Invoking this method will cause the unmerged child connections to merge their audio | 
|  | * streams. | 
|  | */ | 
|  | public void merge() { | 
|  | try { | 
|  | mConnectionService.mergeConference(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Swaps the active audio stream between the conference's child {@link RemoteConnection}s. | 
|  | * This should be invoked only if the conference contains the capability | 
|  | * {@link Connection#CAPABILITY_SWAP_CONFERENCE}, otherwise it is a no-op. This is only used by | 
|  | * {@link ConnectionService}s that create conferences for connections that do not yet have | 
|  | * their audio streams merged; this is a common pattern for CDMA conference calls, but the | 
|  | * capability is not used for GSM and SIP conference calls. Invoking this method will change the | 
|  | * active audio stream to a different child connection. | 
|  | */ | 
|  | public void swap() { | 
|  | try { | 
|  | mConnectionService.swapConference(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Puts the conference on hold. | 
|  | */ | 
|  | public void hold() { | 
|  | try { | 
|  | mConnectionService.hold(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Unholds the conference call. | 
|  | */ | 
|  | public void unhold() { | 
|  | try { | 
|  | mConnectionService.unhold(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the {@link DisconnectCause} for the conference if it is in the state | 
|  | * {@link Connection#STATE_DISCONNECTED}. If the conference is not disconnected, this will | 
|  | * return null. | 
|  | * | 
|  | * @return The disconnect cause. | 
|  | */ | 
|  | public DisconnectCause getDisconnectCause() { | 
|  | return mDisconnectCause; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Requests that the conference start playing the specified DTMF tone. | 
|  | * | 
|  | * @param digit The digit for which to play a DTMF tone. | 
|  | */ | 
|  | public void playDtmfTone(char digit) { | 
|  | try { | 
|  | mConnectionService.playDtmfTone(mId, digit); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Stops the most recent request to play a DTMF tone. | 
|  | * | 
|  | * @see #playDtmfTone | 
|  | */ | 
|  | public void stopDtmfTone() { | 
|  | try { | 
|  | mConnectionService.stopDtmfTone(mId); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Request to change the conference's audio routing to the specified state. The specified state | 
|  | * can include audio routing (Bluetooth, Speaker, etc) and muting state. | 
|  | * | 
|  | * @see android.telecom.AudioState | 
|  | * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead. | 
|  | * @hide | 
|  | */ | 
|  | @SystemApi | 
|  | @Deprecated | 
|  | public void setAudioState(AudioState state) { | 
|  | setCallAudioState(new CallAudioState(state)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Request to change the conference's audio routing to the specified state. The specified state | 
|  | * can include audio routing (Bluetooth, Speaker, etc) and muting state. | 
|  | */ | 
|  | public void setCallAudioState(CallAudioState state) { | 
|  | try { | 
|  | mConnectionService.onCallAudioStateChanged(mId, state); | 
|  | } catch (RemoteException e) { | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Returns a list of independent connections that can me merged with this conference. | 
|  | * | 
|  | * @return A list of conferenceable connections. | 
|  | */ | 
|  | public List<RemoteConnection> getConferenceableConnections() { | 
|  | return mUnmodifiableConferenceableConnections; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Register a callback through which to receive state updates for this conference. | 
|  | * | 
|  | * @param callback The callback to notify of state changes. | 
|  | */ | 
|  | public final void registerCallback(Callback callback) { | 
|  | registerCallback(callback, new Handler()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Registers a callback through which to receive state updates for this conference. | 
|  | * Callbacks will be notified using the specified handler, if provided. | 
|  | * | 
|  | * @param callback The callback to notify of state changes. | 
|  | * @param handler The handler on which to execute the callbacks. | 
|  | */ | 
|  | public final void registerCallback(Callback callback, Handler handler) { | 
|  | unregisterCallback(callback); | 
|  | if (callback != null && handler != null) { | 
|  | mCallbackRecords.add(new CallbackRecord(callback, handler)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Unregisters a previously registered callback. | 
|  | * | 
|  | * @see #registerCallback | 
|  | * | 
|  | * @param callback The callback to unregister. | 
|  | */ | 
|  | public final void unregisterCallback(Callback callback) { | 
|  | if (callback != null) { | 
|  | for (CallbackRecord<Callback> record : mCallbackRecords) { | 
|  | if (record.getCallback() == callback) { | 
|  | mCallbackRecords.remove(record); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |