| /* |
| * 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); |
| } |
| } |