| /* | |
| * 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 android.annotation.Nullable; | |
| import android.annotation.SystemApi; | |
| import android.os.Bundle; | |
| import android.os.Handler; | |
| import android.os.RemoteException; | |
| import com.android.internal.telecom.IConnectionService; | |
| 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 */ | |
| RemoteConference(DisconnectCause disconnectCause) { | |
| mId = "NULL"; | |
| mConnectionService = null; | |
| mState = Connection.STATE_DISCONNECTED; | |
| mDisconnectCause = disconnectCause; | |
| } | |
| /** @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 (extras == null) { | |
| return; | |
| } | |
| 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, null /*Session.Info*/); | |
| } 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(), null /*Session.Info*/); | |
| } 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, null /*Session.Info*/); | |
| } 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, null /*Session.Info*/); | |
| } catch (RemoteException e) { | |
| } | |
| } | |
| /** | |
| * Puts the conference on hold. | |
| */ | |
| public void hold() { | |
| try { | |
| mConnectionService.hold(mId, null /*Session.Info*/); | |
| } catch (RemoteException e) { | |
| } | |
| } | |
| /** | |
| * Unholds the conference call. | |
| */ | |
| public void unhold() { | |
| try { | |
| mConnectionService.unhold(mId, null /*Session.Info*/); | |
| } 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, null /*Session.Info*/); | |
| } catch (RemoteException e) { | |
| } | |
| } | |
| /** | |
| * Stops the most recent request to play a DTMF tone. | |
| * | |
| * @see #playDtmfTone | |
| */ | |
| public void stopDtmfTone() { | |
| try { | |
| mConnectionService.stopDtmfTone(mId, null /*Session.Info*/); | |
| } 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, null /*Session.Info*/); | |
| } 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; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Create a {@link RemoteConference} represents a failure, and which will | |
| * be in {@link Connection#STATE_DISCONNECTED}. | |
| * | |
| * @param disconnectCause The disconnect cause. | |
| * @return a failed {@link RemoteConference} | |
| * @hide | |
| */ | |
| public static RemoteConference failure(DisconnectCause disconnectCause) { | |
| return new RemoteConference(disconnectCause); | |
| } | |
| } |